Skip to content

Conversation

@AbanoubGhadban
Copy link
Collaborator

@AbanoubGhadban AbanoubGhadban commented Sep 29, 2025

Summary

This PR implements the separation of JavaScript Pro functionality from the core React-on-Rails package into a separate react-on-rails-pro package, as outlined in Phase 3 of the monorepo merger plan.

Architecture Decisions

  • Package Strategy: Pro package uses core as dependency (not peer dependency)
  • Versioning: Caret range strategy (^16.1.0) following React model
  • Registry Architecture: Dual system with direct imports (MIT→MIT, Pro→Pro)
  • Code Reuse: Pro features layered over Core functionality for DRY

Implementation Plan

Created comprehensive implementation plan in docs/JS_PRO_PACKAGE_SEPARATION_PLAN.md with:

  • 10 major steps broken into 30+ checkpoints
  • Detailed success criteria and validation steps
  • Comprehensive testing strategy
  • Rollback procedures

Key Changes (To Be Implemented)

New Structure:

packages/
├── react-on-rails/ (MIT - Core functionality)
│   ├── src/ComponentRegistry.ts (simple sync)
│   ├── src/StoreRegistry.ts (simple sync) 
│   └── src/ClientRenderer.ts (simple sync)
└── react-on-rails-pro/ (Pro - Enhanced functionality)
    ├── src/ComponentRegistry.ts (advanced async)
    ├── src/StoreRegistry.ts (advanced async)
    ├── src/ClientSideRenderer.ts (advanced async)
    └── src/index.ts (exports all core + pro)

User Experience:

  • Core users: import ReactOnRails from 'react-on-rails' (sync features only)
  • Pro users: import ReactOnRails from 'react-on-rails-pro' (all features)

Implementation Status

✅ Comprehensive implementation plan created
⏳ Step-by-step implementation in progress

License Compliance

  • Core package: MIT licensed (simple registries, basic features)
  • Pro package: Pro licensed (advanced registries, async features, immediate hydration)
  • Clean separation maintained throughout

🤖 Generated with Claude Code


This change is Reviewable

Summary by CodeRabbit

  • New Features

    • Adds a standalone Pro package with server-component support, streaming helpers, Pro client/server entry points, and utility helpers; new client rendering APIs (render single, render all, component-loaded promise) and automatic unmount/cleanup.
    • Public component and store registries for runtime registration and hydration.
  • Refactor

    • Clear core vs Pro separation; factory-based initialization and reduced global surface; Pro-only APIs moved out of core.
  • Documentation

    • Comprehensive migration plan, changelog, and license updates.
  • Tests

    • Expanded package-scoped tests and new Jest setup.
  • Chores

    • Workspace, build, CI, lint, tsconfig, and .gitignore updates.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 29, 2025

Walkthrough

Splits Pro/RSC features into a new package packages/react-on-rails-pro, adds factory-based core/base builders and in-memory registries, refactors client renderer/startup and exports, updates workspace/build/test/config/licensing, and updates tests and dummy apps to reference the Pro package.

Changes

Cohort / File(s) Summary
New Pro package & metadata
packages/react-on-rails-pro/package.json, packages/react-on-rails-pro/tsconfig.json
Adds react-on-rails-pro package manifest, exports, peers, scripts and TS config (outDir ./lib).
Monorepo & root config
package.json, tsconfig.json, .gitignore, knip.ts, eslint.config.ts
Adds pro workspace, adjusts root build script, TS declaration/sourceMap changes, new .gitignore patterns, knip/eslint updates for pro package.
Core factory & base builders
packages/react-on-rails/src/createReactOnRails.ts, src/base/client.ts, src/base/full.ts, src/context.ts
Introduces factory createReactOnRails and base client/full creators; widens global type and provides core stubs.
Core public entries & startup
packages/react-on-rails/src/ReactOnRails.client.ts, src/ReactOnRails.full.ts, src/ReactOnRails.node.ts, src/clientStartup.ts
Switches to factory-created exports, removes direct Pro streaming wiring from core node entry, simplifies startup to core page-load render.
Registries (core)
packages/react-on-rails/src/ComponentRegistry.ts, packages/react-on-rails/src/StoreRegistry.ts
Adds default-export in-memory registries with register/get/set/clear APIs and Pro-only placeholders.
Client renderer
packages/react-on-rails/src/ClientRenderer.ts, packages/react-on-rails/tests/ClientRenderer.test.ts
New ClientRenderer with renderComponent/renderAllComponents/reactOnRailsComponentLoaded, hydration/render decision, root tracking and unmount on page unload; tests added/updated.
Server render adjustments
packages/react-on-rails/src/serverRenderReactComponent.ts, packages/react-on-rails/tests/serverRenderReactComponent.test.ts
Component lookup switched to globalThis.ReactOnRails.getComponent; tests updated to provide global ReactOnRails.
Pro public entries & wiring
packages/react-on-rails-pro/src/ReactOnRails.client.ts, src/ReactOnRails.full.ts, src/ReactOnRails.node.ts, src/createReactOnRailsPro.ts
New Pro client/full/node entries, createReactOnRailsPro implementation, Pro streaming API (streamServerRenderedReactComponent) wired on Pro node export, re-exports core types.
Pro modules — imports, types, docs
packages/react-on-rails-pro/src/*
Many Pro modules updated to import from react-on-rails/*, add license headers/JSDoc @public tags, add utils/loadJsonFile and small helpers.
Exports & package surface (core)
packages/react-on-rails/package.json
Reworks root exports: removes many Pro entry aliases, adds explicit core exports (types, context, createReactOutput, reactHydrateOrRender, serverRenderReactComponent, internal base mappings).
Tests & Jest setup
packages/react-on-rails/tests/*, packages/react-on-rails-pro/tests/*, packages/react-on-rails-pro/jest.config.js, packages/react-on-rails-pro/tests/jest.setup.js, packages/react-on-rails/tests/jest.setup.js
Tests updated for default-export registries/APIs; added Pro Jest config and heavy polyfills; core Jest setup simplified (removed Node fetch->web conversion).
Dummy apps & linking
react_on_rails_pro/spec/dummy/**, react_on_rails_pro/spec/dummy/package.json
Dummy apps updated to import react-on-rails-pro; yalc/link scripts adjusted for pro workflow.
Ruby generator & specs
lib/react_on_rails/packs_generator.rb, spec/.../packs_generator_spec.rb, script/convert, spec/dummy/config/initializers/react_on_rails.rb
Generator now computes npm package (react-on-rails vs react-on-rails-pro), tests and scripts updated; initializer changes added for hydration/loading strategy.
Licensing & changelog
LICENSE.md, CHANGELOG.md, packages/react-on-rails/src/pro/NOTICE
Moves license boundaries to package-level, updates changelog migration notes, removes legacy Pro NOTICE file.
CI & workflows
.github/workflows/package-js-tests.yml, .github/workflows/lint-js-and-ruby.yml
Adds JS build step for renderer/pro package and adjusts lint invocation to exclude some internal entries.
Misc utilities & helpers
packages/react-on-rails-pro/src/utils.ts, packages/react-on-rails-pro/src/loadJsonFile.ts
New utility functions (fetch wrapper, createRSCPayloadKey, wrapInNewPromise, extractErrorMessage) and loadJsonFile with license header.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Browser
  participant Import as "import react-on-rails / react-on-rails-pro"
  participant Factory as "createReactOnRails / createReactOnRailsPro"
  participant Base as "createBase*(client/full)"
  participant Reg as "Component/Store Registries"
  participant Global as "globalThis.ReactOnRails"
  participant Startup as "clientStartup"

  Browser->>Import: import client/full entry
  Import->>Factory: call factory(baseCreator, currentGlobal?)
  Factory->>Base: create base object using Registries
  Base-->>Factory: return base object
  Factory->>Global: assign globalThis.ReactOnRails (if unset)
  Factory->>Startup: schedule/register page-load hooks
  Startup->>Reg: query components/stores
  Startup->>Global: call renderAllComponents()
  Global->>Browser: hydrate/render components
Loading
sequenceDiagram
  autonumber
  participant Server as Node
  participant CoreNode as "react-on-rails.node"
  participant ProNode as "react-on-rails-pro.node"
  participant Stream as "streamServerRenderedReactComponent"

  Server->>CoreNode: import react-on-rails
  CoreNode-->>Server: Pro streaming APIs -> stub/throws (requires pro)
  Server->>ProNode: import react-on-rails-pro
  ProNode->>Stream: provide streaming implementation
  Stream-->>Server: stream SSR result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related issues

  • Monorepo of pro and basic #1765 — Directly matches the PR's objective: split Pro JS into a separate react-on-rails-pro package and update packaging/licensing/workspace.

Possibly related PRs

Suggested reviewers

  • justin808
  • alexeyr-ci2
  • Judahmeek

Poem

I’m a rabbit in the tree of code,
I split the pro carrots off the road.
Registries hop, factories sing,
Tests and builds and CI spring.
🥕🐇

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 clearly states the key action—splitting the JS Pro code into a separate package—and directly corresponds to the main change described in the pull request, making it concise and informative for anyone reviewing the history.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch split-js-pro-package

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4b49bfd and 92112f1.

📒 Files selected for processing (2)
  • docs/MONOREPO_MERGER_PLAN.md (1 hunks)
  • docs/MONOREPO_MERGER_PLAN_REF.md (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • docs/MONOREPO_MERGER_PLAN.md
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Prettier is the sole authority for formatting all non-Ruby files; never manually format them

Files:

  • docs/MONOREPO_MERGER_PLAN_REF.md
⏰ 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). (10)
  • GitHub Check: dummy-app-integration-tests (3.2, 20)
  • GitHub Check: dummy-app-integration-tests (3.4, 22)
  • GitHub Check: markdown-link-check
  • GitHub Check: rspec-package-tests (3.4, minimum)
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: rspec-package-tests (3.2, latest)
  • GitHub Check: build
  • GitHub Check: rspec-package-tests (3.2, minimum)
  • GitHub Check: claude-review
  • GitHub Check: build-and-test

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.

@claude
Copy link

claude bot commented Sep 29, 2025

Code Review for PR #1841: Split JS Pro Code to Separate Package

I've reviewed the comprehensive implementation plan for separating the JavaScript Pro functionality, and I'd like to provide detailed feedback across several areas:

Strengths

  1. Excellent Architecture Decisions

    • The dependency strategy (Pro depends on Core, not peer dependency) follows React's proven model
    • Caret versioning strategy (^16.1.0) provides good flexibility while maintaining compatibility
    • Clear separation between MIT and Pro licensed code
  2. Comprehensive Planning

    • 10 major steps with 30+ detailed checkpoints provide excellent tracking
    • Success validation criteria at each step ensures quality
    • Rollback strategy with git-based approach is practical
  3. DRY Principle Implementation

    • Smart reuse of core rendering logic in Pro package
    • Layered approach minimizes code duplication

⚠️ Areas for Improvement & Concerns

1. Testing Strategy Gaps

  • Missing Performance Testing: No benchmarks for comparing core vs pro package performance
  • Recommendation: Add performance testing checkpoints, especially for async registry operations
  • Missing Edge Cases: No testing for version mismatch scenarios between core and pro packages
  • Recommendation: Add tests for compatibility when pro package uses older/newer core versions

2. Security Considerations

  • License Enforcement: No automated checks to prevent accidental MIT/Pro code mixing
  • Recommendation: Add pre-commit hooks or CI checks to validate license boundaries
  • Package Publication: No mention of NPM registry security (2FA, publish tokens)
  • Recommendation: Document secure publishing process for both packages

3. Performance Considerations

  • Bundle Size Impact: Pro package importing all of core could increase bundle size unnecessarily
  • Recommendation: Consider tree-shaking optimization or selective imports
  • Async Registry Overhead: CallbackRegistry could introduce memory leaks if callbacks aren't cleaned up
  • Recommendation: Add cleanup mechanisms and memory profiling tests

4. Migration Path Details

  • Breaking Change Risk: Step 3.1 mentions "pro-only methods throw helpful errors" but doesn't specify the exact error messages
  • Recommendation: Document all error messages and provide migration guide with before/after code examples
  • Gradual Migration: No strategy for users who want to migrate feature-by-feature
  • Recommendation: Consider supporting both packages simultaneously during migration period

🐛 Potential Bugs & Issues

  1. globalThis.ReactOnRails Race Condition

    • Both packages set globalThis.ReactOnRails, potential for conflicts if both loaded
    • Recommendation: Add version checking and warning when both packages detected
  2. TypeScript Type Conflicts

    • No mention of handling TypeScript declaration merging between packages
    • Recommendation: Test TypeScript projects importing both packages to catch type conflicts
  3. Build Order Dependency

    • Step 7.1 mentions "core built first" but no explicit dependency ordering
    • Recommendation: Use workspace tools like nx or explicit build scripts

📊 Test Coverage Concerns

  1. Integration Test Gaps:

    • No testing with real Rails applications beyond dummy app
    • No testing with different webpack/esbuild configurations
    • No testing with SSR/hydration edge cases
  2. Missing Test Scenarios:

    • Concurrent async registry operations
    • Error recovery in CallbackRegistry
    • Package version mismatch handling
    • Browser compatibility testing

🔒 License Compliance

The license separation looks good, but consider:

  • Automated license header validation in CI
  • Clear documentation about which features require Pro license
  • License key validation mechanism (if applicable)

📝 Documentation Needs

  1. User-Facing Documentation:

    • Migration guide from core to pro
    • Feature comparison table
    • Performance implications documentation
  2. Developer Documentation:

    • Contributing guidelines for each package
    • Architecture decision records (ADRs)
    • Debugging guide for async registry issues

💡 Suggestions for Implementation

  1. Phased Rollout:

    • Consider feature flags to gradually enable pro features
    • Beta release channel for early adopters
    • Telemetry to monitor adoption and issues
  2. Monitoring & Observability:

    • Add logging for async registry operations
    • Performance metrics collection
    • Error tracking for migration issues
  3. Developer Experience:

    • Create codemods for automated migration
    • VS Code snippets for common patterns
    • Debugging tools for async registry state

Overall Assessment

This is a well-thought-out plan with excellent structure and attention to detail. The architectural decisions are sound and follow industry best practices. With the additions suggested above, particularly around testing, security, and migration support, this implementation should provide a smooth transition while maintaining the high quality expected of the React on Rails ecosystem.

The step-by-step approach with checkpoints will help ensure nothing is missed during implementation. I particularly appreciate the focus on maintaining backward compatibility and the clear license boundaries.

Recommendation: Proceed with implementation, incorporating the suggested improvements, especially the testing enhancements and migration tooling.

🤖 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: 7

🧹 Nitpick comments (6)
packages/react-on-rails/src/ReactOnRails.client.ts (1)

99-101: Remove unused parameter or prefix with underscore.

The storeName parameter is defined but never used in the function body. Since this is a stub that throws an error, you can either remove it or prefix with underscore to indicate it's intentionally unused.

Apply this diff:

-  reactOnRailsStoreLoaded(storeName: string): Promise<void> {
+  reactOnRailsStoreLoaded(_storeName: string): Promise<void> {
     throw new Error('reactOnRailsStoreLoaded requires react-on-rails-pro package');
   },
packages/react-on-rails/src/ComponentRegistry.ts (1)

10-31: Consider preventing duplicate registration instead of just warning.

The current implementation warns when a component is already registered (line 12-14) but still proceeds to overwrite it. This could mask bugs where components are unintentionally registered twice.

Consider either:

  1. Throwing an error to prevent accidental overwrites, or
  2. Add a comment explaining why overwriting is intentional

If overwriting is the desired behavior, consider renaming the method or adding an explicit force parameter to make the intent clear.

packages/react-on-rails/src/StoreRegistry.ts (1)

11-27: Consider preventing duplicate registration for consistency.

Similar to ComponentRegistry, this implementation warns about duplicate registration (line 13-15) but proceeds to overwrite. For consistency and to prevent accidental bugs, consider the same approach across both registries.

Either:

  1. Throw an error to prevent overwrites, or
  2. Document why overwriting is intentional

This should align with the decision made for ComponentRegistry to maintain a consistent API pattern.

packages/react-on-rails/tests/StoreRegistry.test.js (1)

48-52: Clarify error message wording in getStoreGenerator

The implementation currently throws

Could not find store registered with name '${name}'. Registered store names include…

when getStoreGenerator fails. For clarity, consider updating both the thrown message and the test to read “store generator” instead of “store.”

packages/react-on-rails-pro/package.json (2)

49-58: Consider using a version range for react-on-rails-rsc peer dependency.

The react-on-rails-rsc peer dependency specifies an exact version (19.0.2), which may be overly strict and could prevent users from using compatible patch or minor versions. Since it's an optional peer dependency, consider using a more flexible version range (e.g., ^19.0.2 or ~19.0.2) to allow compatible updates while maintaining the optional flag.

Apply this diff if a caret range is appropriate:

   "peerDependencies": {
     "react": ">= 16",
     "react-dom": ">= 16",
-    "react-on-rails-rsc": "19.0.2"
+    "react-on-rails-rsc": "^19.0.2"
   },

59-61: Consider including additional files in the distribution.

Only the lib directory is currently included in the published package. Consider whether additional files should be distributed, such as:

  • README.md (usage documentation)
  • LICENSE or LICENSE.txt (Pro license terms)
  • CHANGELOG.md (version history)

If these files should be included:

   "files": [
-    "lib"
+    "lib",
+    "README.md",
+    "LICENSE"
   ],
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 58e1408 and 0bcc5de.

⛔ Files ignored due to path filters (1)
  • packages/react-on-rails-pro/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (12)
  • docs/JS_PRO_PACKAGE_SEPARATION_PLAN.md (1 hunks)
  • packages/react-on-rails-pro/package.json (1 hunks)
  • packages/react-on-rails-pro/tsconfig.json (1 hunks)
  • packages/react-on-rails/src/ClientRenderer.ts (1 hunks)
  • packages/react-on-rails/src/ComponentRegistry.ts (1 hunks)
  • packages/react-on-rails/src/ReactOnRails.client.ts (2 hunks)
  • packages/react-on-rails/src/ReactOnRails.node.ts (1 hunks)
  • packages/react-on-rails/src/StoreRegistry.ts (1 hunks)
  • packages/react-on-rails/src/serverRenderReactComponent.ts (2 hunks)
  • packages/react-on-rails/tests/ClientRenderer.test.ts (1 hunks)
  • packages/react-on-rails/tests/ComponentRegistry.test.js (4 hunks)
  • packages/react-on-rails/tests/StoreRegistry.test.js (5 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/react-on-rails-pro/tsconfig.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/JS_PRO_PACKAGE_SEPARATION_PLAN.md
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Prettier is the sole authority for formatting all non-Ruby files; never manually format them

Files:

  • packages/react-on-rails/src/StoreRegistry.ts
  • packages/react-on-rails/src/ClientRenderer.ts
  • packages/react-on-rails/tests/ClientRenderer.test.ts
  • packages/react-on-rails/src/ReactOnRails.client.ts
  • packages/react-on-rails/src/serverRenderReactComponent.ts
  • packages/react-on-rails/tests/StoreRegistry.test.js
  • packages/react-on-rails-pro/package.json
  • packages/react-on-rails/src/ComponentRegistry.ts
  • packages/react-on-rails/tests/ComponentRegistry.test.js
  • packages/react-on-rails/src/ReactOnRails.node.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for JS/TS code (lint via rake lint or yarn lint)

Files:

  • packages/react-on-rails/src/StoreRegistry.ts
  • packages/react-on-rails/src/ClientRenderer.ts
  • packages/react-on-rails/tests/ClientRenderer.test.ts
  • packages/react-on-rails/src/ReactOnRails.client.ts
  • packages/react-on-rails/src/serverRenderReactComponent.ts
  • packages/react-on-rails/tests/StoreRegistry.test.js
  • packages/react-on-rails/src/ComponentRegistry.ts
  • packages/react-on-rails/tests/ComponentRegistry.test.js
  • packages/react-on-rails/src/ReactOnRails.node.ts
🧠 Learnings (4)
📚 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:

  • packages/react-on-rails/src/ReactOnRails.client.ts
  • packages/react-on-rails/src/ReactOnRails.node.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:

  • packages/react-on-rails/src/ReactOnRails.client.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:

  • packages/react-on-rails/src/serverRenderReactComponent.ts
  • packages/react-on-rails/src/ReactOnRails.node.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:

  • packages/react-on-rails/src/ReactOnRails.node.ts
🧬 Code graph analysis (6)
packages/react-on-rails/src/StoreRegistry.ts (1)
packages/react-on-rails/src/types/index.ts (2)
  • StoreGenerator (184-184)
  • Store (183-183)
packages/react-on-rails/src/ClientRenderer.ts (3)
packages/react-on-rails/src/createReactOutput.ts (1)
  • createReactOutput (36-92)
packages/react-on-rails/src/isServerRenderResult.ts (1)
  • isServerRenderHash (8-17)
packages/react-on-rails/src/context.ts (1)
  • getRailsContext (14-32)
packages/react-on-rails/tests/ClientRenderer.test.ts (1)
packages/react-on-rails/src/ClientRenderer.ts (2)
  • renderComponent (113-127)
  • reactOnRailsComponentLoaded (134-137)
packages/react-on-rails/src/ReactOnRails.client.ts (1)
packages/react-on-rails/src/ClientRenderer.ts (1)
  • reactOnRailsComponentLoaded (134-137)
packages/react-on-rails/src/ComponentRegistry.ts (2)
packages/react-on-rails/src/ReactOnRails.client.ts (1)
  • registeredComponents (176-178)
packages/react-on-rails/src/isRenderFunction.ts (1)
  • isRenderFunction (11-31)
packages/react-on-rails/src/ReactOnRails.node.ts (1)
packages/react-on-rails/src/types/index.ts (1)
  • ReactOnRails (277-345)
🪛 ast-grep (0.39.5)
packages/react-on-rails/tests/ClientRenderer.test.ts

[warning] 15-15: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: domNode.innerHTML = '

Rendered: test
'
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 26-26: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: document.body.innerHTML = ''
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 27-27: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: document.head.innerHTML = ''
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 15-15: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: domNode.innerHTML = '

Rendered: test
'
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)

🪛 ESLint
packages/react-on-rails/src/ReactOnRails.client.ts

[error] 99-99: 'storeName' is defined but never used.

(@typescript-eslint/no-unused-vars)

🪛 GitHub Actions: JS unit tests for Renderer package
packages/react-on-rails/src/serverRenderReactComponent.ts

[error] 150-150: Cannot read properties of undefined (reading 'getComponent') while attempting to render a registered React component during server-side rendering.

⏰ 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: markdown-link-check
  • GitHub Check: rspec-package-tests (3.2, latest)
  • GitHub Check: rspec-package-tests (3.2, minimum)
  • GitHub Check: rspec-package-tests (3.4, minimum)
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: build-and-test
🔇 Additional comments (26)
packages/react-on-rails/src/ReactOnRails.node.ts (1)

3-6: LGTM! Pro feature correctly gated.

The stub implementation properly prevents access to pro-only streaming functionality and provides a clear error message directing users to the pro package.

packages/react-on-rails/src/ReactOnRails.client.ts (2)

3-5: LGTM! Import structure updated correctly.

The imports now properly reference the new ClientRenderer module and default exports from the registries, aligning with the architectural changes for pro package separation.


95-97: LGTM! Proper delegation to ClientRenderer.

The delegation to the imported reactOnRailsComponentLoaded function maintains the public API while cleanly separating concerns.

packages/react-on-rails/tests/ClientRenderer.test.ts (1)

1-206: LGTM! Comprehensive test coverage.

The test suite properly covers the main scenarios for ClientRenderer:

  • Rendering registered components
  • Graceful handling of missing Rails context and DOM elements
  • Renderer function integration
  • API alias verification for reactOnRailsComponentLoaded

The static analysis warnings about innerHTML usage are false positives—the test code uses hardcoded safe strings for mocking and DOM cleanup, which is appropriate for unit tests.

packages/react-on-rails/src/ClientRenderer.ts (3)

31-60: LGTM! Clean renderer delegation logic.

The renderer detection and delegation logic is well-structured with appropriate trace logging for debugging.


113-127: LGTM! Clear and robust public API.

The renderComponent function provides a clean entry point with appropriate early returns for missing context or elements.


134-137: LGTM! Proper Promise-returning wrapper.

The reactOnRailsComponentLoaded function correctly wraps the synchronous renderComponent with a Promise for API compatibility.

packages/react-on-rails/tests/ComponentRegistry.test.js (4)

17-93: LGTM! Test updates correctly reflect the new API.

The tests properly validate the updated ComponentRegistry API with default export usage, single-object registration, and Map-based retrieval patterns.


125-132: LGTM! Proper console spy pattern for duplicate registration warning.

The test correctly mocks and restores console.warn to verify the duplicate registration warning behavior.


134-138: LGTM! Correctly validates pro-only method boundary.

The test properly verifies that calling getOrWaitForComponent throws an appropriate error indicating the pro package is required.


140-147: LGTM! Map interface validation is thorough.

The test properly validates the Map interface returned by components(), including type checking and expected operations.

packages/react-on-rails/src/ComponentRegistry.ts (3)

21-22: LGTM! Renderer detection logic is correct.

The logic correctly determines isRenderer only when both renderFunction is true AND the component has exactly 3 parameters, which aligns with the renderer function contract.


37-46: LGTM! Error message provides helpful debugging context.

The get method provides clear error messages including all registered component names, which will help developers quickly identify registration issues.


63-65: LGTM! Clear pro-only boundary enforcement.

The placeholder method clearly communicates that getOrWaitForComponent requires the pro package, guiding users to upgrade if they need async component registration.

packages/react-on-rails/src/StoreRegistry.ts (2)

44-50: LGTM! Excellent error message for missing store hydration.

The multi-line error message at lines 44-50 provides clear guidance on common causes of missing hydrated stores, including specific troubleshooting steps for server rendering scenarios.


119-125: LGTM! Pro-only methods provide excellent upgrade guidance.

Both getOrWaitForStore and getOrWaitForStoreGenerator clearly communicate the pro-only nature of async waiting features and provide helpful links to upgrade information.

Also applies to: 133-139

packages/react-on-rails/tests/StoreRegistry.test.js (6)

3-3: LGTM: Import updated to match new module structure.

The import correctly uses the default export from the core StoreRegistry module, aligning with the package separation architecture.


18-21: LGTM: Test isolation improved.

Clearing both the stores() and storeGenerators() maps ensures proper test isolation and prevents cross-test pollution.


23-30: LGTM: Error messages updated for API rename.

The error messages correctly reflect the renamed registerStores API (previously registerStoreGenerators).


69-74: LGTM: Test setup improved for error message validation.

Adding setStore('someStore', {}) before the assertion ensures the error message correctly lists existing hydrated store names, providing better test coverage for the error case.


90-100: LGTM: Pro-only feature gating verified.

These tests ensure that the core package correctly throws errors when attempting to use Pro-only async store methods, maintaining clear license boundaries between core and Pro functionality.


102-119: LGTM: Accessor methods well-tested.

The new tests for storeGenerators() and stores() accessors provide good coverage for inspecting registry state, verifying both the presence of registered entries and the correct map sizes.

packages/react-on-rails-pro/package.json (4)

1-5: LGTM! Package metadata is well-structured.

The package metadata correctly identifies this as the Pro package with an UNLICENSED (proprietary) license, ESM module type, and comprehensive repository information.

Also applies to: 16-29


3-3: No change required: Pro and core versions are aligned

The core package is at version 16.1.1, and the ^16.1.0 range correctly includes patch 16.1.1, so no dependency update is needed.


6-15: Remove outdated nps availability note.

nps (^5.9.3) is declared in the root package.json and hoisted to workspaces, so the prepack/prepare scripts will resolve correctly.


30-45: Remove obsolete comment on non-existent package path.

The file packages/react-on-rails-pro/package.json is not present in the repository; the original review refers to a non-existent path and should be ignored or updated to match the actual package structure.

Likely an incorrect or invalid review comment.

Comment on lines 66 to 114
function renderElement(el: Element, railsContext: RailsContext): void {
// This must match lib/react_on_rails/helper.rb
const name = el.getAttribute('data-component-name') || '';
const domNodeId = domNodeIdForEl(el);
const props = el.textContent !== null ? (JSON.parse(el.textContent) as Record<string, unknown>) : {};
const trace = el.getAttribute('data-trace') === 'true';

try {
const domNode = document.getElementById(domNodeId);
if (domNode) {
const componentObj = ComponentRegistry.get(name);
if (delegateToRenderer(componentObj, props, railsContext, domNodeId, trace)) {
return;
}

// Hydrate if available and was server rendered
const shouldHydrate = !!domNode.innerHTML;

const reactElementOrRouterResult = createReactOutput({
componentObj,
props,
domNodeId,
trace,
railsContext,
shouldHydrate,
});

if (isServerRenderHash(reactElementOrRouterResult)) {
throw new Error(`\
You returned a server side type of react-router error: ${JSON.stringify(reactElementOrRouterResult)}
You should return a React.Component always for the client side entry point.`);
} else {
reactHydrateOrRender(domNode, reactElementOrRouterResult as ReactElement, shouldHydrate);
}
}
} catch (e: unknown) {
const error = e as Error;
console.error(error.message);
error.message = `ReactOnRails encountered an error while rendering component: ${name}. See above error message.`;
throw error;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add error handling for JSON parsing in renderElement.

Similar to the store initialization, the JSON parsing of component props at line 70 should have error handling to provide clear diagnostics when props are malformed.

Apply this diff:

   const name = el.getAttribute('data-component-name') || '';
   const domNodeId = domNodeIdForEl(el);
-  const props = el.textContent !== null ? (JSON.parse(el.textContent) as Record<string, unknown>) : {};
+  let props: Record<string, unknown> = {};
+  try {
+    props = el.textContent !== null ? (JSON.parse(el.textContent) as Record<string, unknown>) : {};
+  } catch (e) {
+    throw new Error(`Failed to parse props for component '${name}': ${e instanceof Error ? e.message : String(e)}`);
+  }
   const trace = el.getAttribute('data-trace') === 'true';
📝 Committable suggestion

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

Suggested change
function renderElement(el: Element, railsContext: RailsContext): void {
// This must match lib/react_on_rails/helper.rb
const name = el.getAttribute('data-component-name') || '';
const domNodeId = domNodeIdForEl(el);
const props = el.textContent !== null ? (JSON.parse(el.textContent) as Record<string, unknown>) : {};
const trace = el.getAttribute('data-trace') === 'true';
try {
const domNode = document.getElementById(domNodeId);
if (domNode) {
const componentObj = ComponentRegistry.get(name);
if (delegateToRenderer(componentObj, props, railsContext, domNodeId, trace)) {
return;
}
// Hydrate if available and was server rendered
const shouldHydrate = !!domNode.innerHTML;
const reactElementOrRouterResult = createReactOutput({
componentObj,
props,
domNodeId,
trace,
railsContext,
shouldHydrate,
});
if (isServerRenderHash(reactElementOrRouterResult)) {
throw new Error(`\
You returned a server side type of react-router error: ${JSON.stringify(reactElementOrRouterResult)}
You should return a React.Component always for the client side entry point.`);
} else {
reactHydrateOrRender(domNode, reactElementOrRouterResult as ReactElement, shouldHydrate);
}
}
} catch (e: unknown) {
const error = e as Error;
console.error(error.message);
error.message = `ReactOnRails encountered an error while rendering component: ${name}. See above error message.`;
throw error;
}
}
function renderElement(el: Element, railsContext: RailsContext): void {
// This must match lib/react_on_rails/helper.rb
const name = el.getAttribute('data-component-name') || '';
const domNodeId = domNodeIdForEl(el);
let props: Record<string, unknown> = {};
try {
props = el.textContent !== null
? (JSON.parse(el.textContent) as Record<string, unknown>)
: {};
} catch (e) {
throw new Error(
`Failed to parse props for component '${name}': ${
e instanceof Error ? e.message : String(e)
}`
);
}
const trace = el.getAttribute('data-trace') === 'true';
try {
const domNode = document.getElementById(domNodeId);
if (domNode) {
const componentObj = ComponentRegistry.get(name);
if (
delegateToRenderer(
componentObj,
props,
railsContext,
domNodeId,
trace
)
) {
return;
}
// Hydrate if available and was server rendered
const shouldHydrate = !!domNode.innerHTML;
const reactElementOrRouterResult = createReactOutput({
componentObj,
props,
domNodeId,
trace,
railsContext,
shouldHydrate,
});
if (isServerRenderHash(reactElementOrRouterResult)) {
throw new Error(`\
You returned a server side type of react-router error: ${JSON.stringify(
reactElementOrRouterResult
)}
You should return a React.Component always for the client side entry point.`);
} else {
reactHydrateOrRender(
domNode,
reactElementOrRouterResult as ReactElement,
shouldHydrate
);
}
}
} catch (e: unknown) {
const error = e as Error;
console.error(error.message);
error.message = `ReactOnRails encountered an error while rendering component: ${name}. See above error message.`;
throw error;
}
}
🤖 Prompt for AI Agents
In packages/react-on-rails/src/ClientRenderer.ts around lines 66 to 107, the
JSON.parse of el.textContent can throw for malformed props; wrap the parse in a
try/catch (or pre-validate) to catch SyntaxError and rethrow a clearer error
that includes the component name, domNodeId and the raw props string; ensure the
new error message is logged/propagated similarly to the existing catch block so
callers get a diagnostic like "Failed to parse props for component X (dom node
Y): <raw string> — original error: <syntax message>" and preserve the original
error as the cause where possible.

Comment on lines +53 to +55
components(): Map<string, RegisteredComponent> {
return registeredComponents;
},
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 | 🟠 Major

Returning internal Map directly exposes mutable state.

Line 54 returns the internal registeredComponents Map directly, allowing external code to mutate the registry (e.g., ComponentRegistry.components().clear() or .set()).

Apply this diff to return a read-only view:

  components(): Map<string, RegisteredComponent> {
-    return registeredComponents;
+    return new Map(registeredComponents);
  },

This returns a shallow copy, preventing external mutation while still allowing read access to the registered components.

📝 Committable suggestion

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

Suggested change
components(): Map<string, RegisteredComponent> {
return registeredComponents;
},
components(): Map<string, RegisteredComponent> {
return new Map(registeredComponents);
},
🤖 Prompt for AI Agents
In packages/react-on-rails/src/ComponentRegistry.ts around lines 53 to 55, the
components() method currently returns the internal registeredComponents Map
directly which exposes mutable internal state; change the method to return a
shallow copy/read-only view instead (for example by returning new
Map(registeredComponents)) so callers can read entries but cannot mutate the
registry's internal Map; update the return type if needed to reflect a
ReadonlyMap.

Comment on lines +36 to +61
getStore(name: string, throwIfMissing = true): Store | undefined {
if (hydratedStores.has(name)) {
return hydratedStores.get(name);
}

const storeKeys = Array.from(hydratedStores.keys()).join(', ');

if (storeKeys.length === 0) {
const msg = `There are no stores hydrated and you are requesting the store ${name}.
This can happen if you are server rendering and either:
1. You do not call redux_store near the top of your controller action's view (not the layout)
and before any call to react_component.
2. You do not render redux_store_hydration_data anywhere on your page.`;
throw new Error(msg);
}

if (throwIfMissing) {
console.log('storeKeys', storeKeys);
throw new Error(
`Could not find hydrated store with name '${name}'. ` +
`Hydrated store names include [${storeKeys}].`,
);
}

return undefined;
},
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 | 🟡 Minor

Remove debug console.log from production code.

Line 53 contains a console.log('storeKeys', storeKeys) statement that appears to be leftover debug code and will pollute production console output.

Apply this diff:

    if (throwIfMissing) {
-      console.log('storeKeys', storeKeys);
      throw new Error(
📝 Committable suggestion

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

Suggested change
getStore(name: string, throwIfMissing = true): Store | undefined {
if (hydratedStores.has(name)) {
return hydratedStores.get(name);
}
const storeKeys = Array.from(hydratedStores.keys()).join(', ');
if (storeKeys.length === 0) {
const msg = `There are no stores hydrated and you are requesting the store ${name}.
This can happen if you are server rendering and either:
1. You do not call redux_store near the top of your controller action's view (not the layout)
and before any call to react_component.
2. You do not render redux_store_hydration_data anywhere on your page.`;
throw new Error(msg);
}
if (throwIfMissing) {
console.log('storeKeys', storeKeys);
throw new Error(
`Could not find hydrated store with name '${name}'. ` +
`Hydrated store names include [${storeKeys}].`,
);
}
return undefined;
},
getStore(name: string, throwIfMissing = true): Store | undefined {
if (hydratedStores.has(name)) {
return hydratedStores.get(name);
}
const storeKeys = Array.from(hydratedStores.keys()).join(', ');
if (storeKeys.length === 0) {
const msg = `There are no stores hydrated and you are requesting the store ${name}.
This can happen if you are server rendering and either:
1. You do not call redux_store near the top of your controller action's view (not the layout)
and before any call to react_component.
2. You do not render redux_store_hydration_data anywhere on your page.`;
throw new Error(msg);
}
if (throwIfMissing) {
throw new Error(
`Could not find hydrated store with name '${name}'. ` +
`Hydrated store names include [${storeKeys}].`,
);
}
return undefined;
},
🤖 Prompt for AI Agents
In packages/react-on-rails/src/StoreRegistry.ts around lines 36 to 61, remove
the leftover debug console.log call on line 53 that prints 'storeKeys' to avoid
polluting production logs; simply delete the console.log('storeKeys', storeKeys)
statement (or replace it with an appropriate debug logger guarded by an
environment check if you need persisted debug output) so the function throws the
error without emitting console output.

Comment on lines +101 to +103
storeGenerators(): Map<string, StoreGenerator> {
return registeredStoreGenerators;
},
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 | 🟠 Major

Returning internal Map directly exposes mutable state.

Line 102 returns the internal registeredStoreGenerators Map directly, allowing external code to mutate the registry.

Apply this diff to return a defensive copy:

  storeGenerators(): Map<string, StoreGenerator> {
-    return registeredStoreGenerators;
+    return new Map(registeredStoreGenerators);
  },
📝 Committable suggestion

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

Suggested change
storeGenerators(): Map<string, StoreGenerator> {
return registeredStoreGenerators;
},
storeGenerators(): Map<string, StoreGenerator> {
return new Map(registeredStoreGenerators);
},
🤖 Prompt for AI Agents
In packages/react-on-rails/src/StoreRegistry.ts around lines 101 to 103, the
method currently returns the internal registeredStoreGenerators Map directly
which exposes mutable internal state; change the implementation to return a
defensive shallow copy (e.g., construct and return a new Map populated from
registeredStoreGenerators) so callers receive a copy they can mutate without
altering the registry, keeping the original Map private.

Comment on lines +109 to +111
stores(): Map<string, Store> {
return hydratedStores;
},
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 | 🟠 Major

Returning internal Map directly exposes mutable state.

Line 110 returns the internal hydratedStores Map directly, allowing external code to mutate the registry.

Apply this diff to return a defensive copy:

  stores(): Map<string, Store> {
-    return hydratedStores;
+    return new Map(hydratedStores);
  },
📝 Committable suggestion

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

Suggested change
stores(): Map<string, Store> {
return hydratedStores;
},
stores(): Map<string, Store> {
return new Map(hydratedStores);
},
🤖 Prompt for AI Agents
In packages/react-on-rails/src/StoreRegistry.ts around lines 109 to 111, the
method currently returns the internal hydratedStores Map directly which exposes
mutable internal state; change it to return a defensive copy by constructing and
returning a new Map based on hydratedStores (e.g. return new
Map(hydratedStores)) so callers get a snapshot and cannot mutate the registry’s
internal Map.

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: 10

♻️ Duplicate comments (2)
packages/react-on-rails/src/ClientRenderer.ts (2)

12-18: Add error handling for store props JSON parsing.

The JSON.parse call can throw a SyntaxError if el.textContent contains malformed JSON. This would result in a cryptic error message that doesn't identify which store failed to parse.

Apply this diff to add descriptive error handling:

 function initializeStore(el: Element, railsContext: RailsContext): void {
   const name = el.getAttribute(REACT_ON_RAILS_STORE_ATTRIBUTE) || '';
-  const props = el.textContent !== null ? (JSON.parse(el.textContent) as Record<string, unknown>) : {};
+  let props: Record<string, unknown> = {};
+  try {
+    props = el.textContent !== null ? (JSON.parse(el.textContent) as Record<string, unknown>) : {};
+  } catch (e) {
+    throw new Error(`Failed to parse props for store '${name}': ${e instanceof Error ? e.message : String(e)}`);
+  }
   const storeGenerator = StoreRegistry.getStoreGenerator(name);
   const store = storeGenerator(props, railsContext);
   StoreRegistry.setStore(name, store);

66-107: Add error handling for component props JSON parsing.

The JSON.parse call at line 70 can throw a SyntaxError if el.textContent contains malformed JSON. While there's a catch block at lines 101-106, it would be clearer to catch the parse error specifically and provide diagnostics including the component name and DOM node ID before the generic error handler runs.

Apply this diff to add specific parse error handling:

   const name = el.getAttribute('data-component-name') || '';
   const domNodeId = domNodeIdForEl(el);
-  const props = el.textContent !== null ? (JSON.parse(el.textContent) as Record<string, unknown>) : {};
+  let props: Record<string, unknown> = {};
+  try {
+    props = el.textContent !== null ? (JSON.parse(el.textContent) as Record<string, unknown>) : {};
+  } catch (e) {
+    throw new Error(`Failed to parse props for component '${name}' (DOM ID: ${domNodeId}): ${e instanceof Error ? e.message : String(e)}`);
+  }
   const trace = el.getAttribute('data-trace') === 'true';
🧹 Nitpick comments (5)
packages/react-on-rails-pro/src/wrapServerComponentRenderer/client.tsx (2)

82-86: Consider more robust SSR detection.

The domNode.innerHTML boolean check may produce false positives if the node contains only whitespace from SSR. Consider using domNode.hasChildNodes() or domNode.innerHTML.trim() for more reliable detection of SSR-rendered content.

Apply this diff for more robust SSR detection:

-    if (domNode.innerHTML) {
+    if (domNode.hasChildNodes()) {
       ReactDOMClient.hydrateRoot(domNode, root, { identifierPrefix: domNodeId });
     } else {
       ReactDOMClient.createRoot(domNode, { identifierPrefix: domNodeId }).render(root);
     }

87-90: Acknowledged technical debt: unused return value.

The TODO comment correctly identifies that the empty string return is only to satisfy the type signature. While not a blocker, this indicates a type/interface mismatch that could be resolved by updating the RenderFunction type to allow void or Promise<void> for client-side rendering scenarios.

Would you like me to open an issue to track refactoring the RenderFunction type to better align with client-side rendering patterns?

packages/react-on-rails-pro/src/ClientSideRenderer.ts (1)

18-28: Fix import ordering to comply with ESLint rules.

The migration to package-scoped imports is correct, but the react-on-rails/pageLifecycle import on line 28 should be grouped with other external package imports (lines 18-25) rather than appearing after local relative imports (lines 26-27).

Apply this diff to fix the import order:

 import type { RailsContext, RegisteredComponent, RenderFunction, Root } from 'react-on-rails/types';
-
 import { getRailsContext, resetRailsContext } from 'react-on-rails/context';
 import createReactOutput from 'react-on-rails/createReactOutput';
 import { isServerRenderHash } from 'react-on-rails/isServerRenderResult';
 import { supportsHydrate, supportsRootApi, unmountComponentAtNode } from 'react-on-rails/reactApis';
 import reactHydrateOrRender from 'react-on-rails/reactHydrateOrRender';
 import { debugTurbolinks } from 'react-on-rails/turbolinksUtils';
+import { onPageLoaded } from 'react-on-rails/pageLifecycle';
+
 import * as StoreRegistry from './StoreRegistry.ts';
 import * as ComponentRegistry from './ComponentRegistry.ts';
-import { onPageLoaded } from 'react-on-rails/pageLifecycle';

As per coding guidelines.

packages/react-on-rails-pro/src/ReactOnRailsRSC.ts (1)

19-34: Fix import ordering to comply with ESLint rules.

The package-scoped imports are correct, but the react-on-rails/loadJsonFile import on line 34 should be grouped with other external package imports (lines 19-27) rather than appearing after the local relative import (lines 29-33).

Apply this diff to fix the import order:

 import {
   RSCRenderParams,
   assertRailsContextWithServerStreamingCapabilities,
   StreamRenderState,
   StreamableComponentResult,
 } from 'react-on-rails/types';
 import ReactOnRails from 'react-on-rails/ReactOnRails.full';
 import handleError from 'react-on-rails/handleError';
 import { convertToError } from 'react-on-rails/serverRenderUtils';
+import loadJsonFile from 'react-on-rails/loadJsonFile';
 
 import {
   streamServerRenderedComponent,
   StreamingTrackers,
   transformRenderStreamChunksToResultObject,
 } from './streamServerRenderedReactComponent.ts';
-import loadJsonFile from 'react-on-rails/loadJsonFile';

As per coding guidelines.

packages/react-on-rails-pro/src/registerServerComponent/server.tsx (1)

41-41: Remove unused component value from destructuring.

The components parameter value is destructured in the loop but never used—only componentName is needed. This suggests either the component implementations should be passed to the wrapper, or the destructuring should only extract the key.

Apply this diff:

-  for (const [componentName] of Object.entries(components)) {
+  for (const componentName of Object.keys(components)) {
     componentsWrappedInRSCRoute[componentName] = wrapServerComponentRenderer((props: unknown) => (
       <RSCRoute componentName={componentName} componentProps={props} />
     ));

Or if component values are needed in the future, keep the current form but add a comment explaining why only the name is used.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0bcc5de and 80a1220.

📒 Files selected for processing (21)
  • docs/JS_PRO_PACKAGE_SEPARATION_PLAN.md (1 hunks)
  • packages/react-on-rails-pro/src/CallbackRegistry.ts (1 hunks)
  • packages/react-on-rails-pro/src/ClientSideRenderer.ts (1 hunks)
  • packages/react-on-rails-pro/src/ComponentRegistry.ts (1 hunks)
  • packages/react-on-rails-pro/src/RSCProvider.tsx (1 hunks)
  • packages/react-on-rails-pro/src/RSCRequestTracker.ts (1 hunks)
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts (2 hunks)
  • packages/react-on-rails-pro/src/StoreRegistry.ts (1 hunks)
  • packages/react-on-rails-pro/src/getReactServerComponent.client.ts (1 hunks)
  • packages/react-on-rails-pro/src/getReactServerComponent.server.ts (1 hunks)
  • packages/react-on-rails-pro/src/injectRSCPayload.ts (1 hunks)
  • packages/react-on-rails-pro/src/registerServerComponent/client.tsx (1 hunks)
  • packages/react-on-rails-pro/src/registerServerComponent/server.rsc.ts (1 hunks)
  • packages/react-on-rails-pro/src/registerServerComponent/server.tsx (1 hunks)
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts (2 hunks)
  • packages/react-on-rails-pro/src/transformRSCStreamAndReplayConsoleLogs.ts (1 hunks)
  • packages/react-on-rails-pro/src/wrapServerComponentRenderer/client.tsx (1 hunks)
  • packages/react-on-rails-pro/src/wrapServerComponentRenderer/server.tsx (1 hunks)
  • packages/react-on-rails/src/ClientRenderer.ts (1 hunks)
  • packages/react-on-rails/src/clientStartup.ts (2 hunks)
  • packages/react-on-rails/src/pro/NOTICE (0 hunks)
💤 Files with no reviewable changes (1)
  • packages/react-on-rails/src/pro/NOTICE
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/JS_PRO_PACKAGE_SEPARATION_PLAN.md
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Prettier is the sole authority for formatting all non-Ruby files; never manually format them

Files:

  • packages/react-on-rails-pro/src/transformRSCStreamAndReplayConsoleLogs.ts
  • packages/react-on-rails-pro/src/wrapServerComponentRenderer/server.tsx
  • packages/react-on-rails-pro/src/getReactServerComponent.client.ts
  • packages/react-on-rails/src/clientStartup.ts
  • packages/react-on-rails-pro/src/CallbackRegistry.ts
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts
  • packages/react-on-rails-pro/src/StoreRegistry.ts
  • packages/react-on-rails-pro/src/wrapServerComponentRenderer/client.tsx
  • packages/react-on-rails-pro/src/RSCRequestTracker.ts
  • packages/react-on-rails-pro/src/registerServerComponent/server.rsc.ts
  • packages/react-on-rails-pro/src/ComponentRegistry.ts
  • packages/react-on-rails-pro/src/ClientSideRenderer.ts
  • packages/react-on-rails-pro/src/RSCProvider.tsx
  • packages/react-on-rails/src/ClientRenderer.ts
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
  • packages/react-on-rails-pro/src/getReactServerComponent.server.ts
  • packages/react-on-rails-pro/src/registerServerComponent/client.tsx
  • packages/react-on-rails-pro/src/registerServerComponent/server.tsx
  • packages/react-on-rails-pro/src/injectRSCPayload.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for JS/TS code (lint via rake lint or yarn lint)

Files:

  • packages/react-on-rails-pro/src/transformRSCStreamAndReplayConsoleLogs.ts
  • packages/react-on-rails-pro/src/wrapServerComponentRenderer/server.tsx
  • packages/react-on-rails-pro/src/getReactServerComponent.client.ts
  • packages/react-on-rails/src/clientStartup.ts
  • packages/react-on-rails-pro/src/CallbackRegistry.ts
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts
  • packages/react-on-rails-pro/src/StoreRegistry.ts
  • packages/react-on-rails-pro/src/wrapServerComponentRenderer/client.tsx
  • packages/react-on-rails-pro/src/RSCRequestTracker.ts
  • packages/react-on-rails-pro/src/registerServerComponent/server.rsc.ts
  • packages/react-on-rails-pro/src/ComponentRegistry.ts
  • packages/react-on-rails-pro/src/ClientSideRenderer.ts
  • packages/react-on-rails-pro/src/RSCProvider.tsx
  • packages/react-on-rails/src/ClientRenderer.ts
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
  • packages/react-on-rails-pro/src/getReactServerComponent.server.ts
  • packages/react-on-rails-pro/src/registerServerComponent/client.tsx
  • packages/react-on-rails-pro/src/registerServerComponent/server.tsx
  • packages/react-on-rails-pro/src/injectRSCPayload.ts
🧠 Learnings (9)
📚 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:

  • packages/react-on-rails-pro/src/transformRSCStreamAndReplayConsoleLogs.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:

  • packages/react-on-rails-pro/src/transformRSCStreamAndReplayConsoleLogs.ts
  • packages/react-on-rails-pro/src/wrapServerComponentRenderer/server.tsx
  • packages/react-on-rails-pro/src/getReactServerComponent.client.ts
  • packages/react-on-rails-pro/src/RSCRequestTracker.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:

  • packages/react-on-rails-pro/src/wrapServerComponentRenderer/server.tsx
  • packages/react-on-rails-pro/src/wrapServerComponentRenderer/client.tsx
  • packages/react-on-rails-pro/src/registerServerComponent/server.rsc.ts
  • packages/react-on-rails-pro/src/registerServerComponent/client.tsx
  • packages/react-on-rails-pro/src/registerServerComponent/server.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:

  • packages/react-on-rails-pro/src/getReactServerComponent.client.ts
  • packages/react-on-rails-pro/src/CallbackRegistry.ts
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts
  • packages/react-on-rails-pro/src/registerServerComponent/server.rsc.ts
  • packages/react-on-rails-pro/src/ClientSideRenderer.ts
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
  • packages/react-on-rails-pro/src/getReactServerComponent.server.ts
  • packages/react-on-rails-pro/src/registerServerComponent/server.tsx
📚 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:

  • packages/react-on-rails-pro/src/getReactServerComponent.client.ts
  • packages/react-on-rails-pro/src/RSCRequestTracker.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:

  • packages/react-on-rails/src/clientStartup.ts
  • packages/react-on-rails-pro/src/registerServerComponent/server.rsc.ts
  • packages/react-on-rails-pro/src/ClientSideRenderer.ts
  • packages/react-on-rails-pro/src/registerServerComponent/client.tsx
  • packages/react-on-rails-pro/src/registerServerComponent/server.tsx
📚 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:

  • packages/react-on-rails/src/clientStartup.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:

  • packages/react-on-rails-pro/src/ReactOnRailsRSC.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:

  • packages/react-on-rails/src/ClientRenderer.ts
🧬 Code graph analysis (2)
packages/react-on-rails/src/clientStartup.ts (3)
packages/react-on-rails/src/ReactOnRails.client.ts (1)
  • reactOnRailsPageLoaded (91-93)
packages/react-on-rails/src/turbolinksUtils.ts (1)
  • debugTurbolinks (14-22)
packages/react-on-rails/src/ClientRenderer.ts (1)
  • renderAllComponents (142-154)
packages/react-on-rails/src/ClientRenderer.ts (4)
packages/react-on-rails/src/createReactOutput.ts (1)
  • createReactOutput (36-92)
packages/react-on-rails/src/isServerRenderResult.ts (1)
  • isServerRenderHash (8-17)
packages/react-on-rails/src/ReactOnRails.client.ts (2)
  • reactHydrateOrRender (67-69)
  • reactOnRailsComponentLoaded (95-97)
packages/react-on-rails/src/context.ts (1)
  • getRailsContext (14-32)
🪛 ESLint
packages/react-on-rails-pro/src/wrapServerComponentRenderer/server.tsx

[error] 19-19: react-on-rails/isRenderFunction import should occur before import of ../getReactServerComponent.server.ts

(import/order)


[error] 20-20: react-on-rails/types import should occur before import of ../getReactServerComponent.server.ts

(import/order)

packages/react-on-rails-pro/src/getReactServerComponent.client.ts

[error] 19-19: react-on-rails/types import should occur before import of ./transformRSCStreamAndReplayConsoleLogs.ts

(import/order)

packages/react-on-rails-pro/src/ReactOnRailsRSC.ts

[error] 34-34: react-on-rails/loadJsonFile import should occur before import of ./streamServerRenderedReactComponent.ts

(import/order)

packages/react-on-rails-pro/src/StoreRegistry.ts

[error] 16-16: react-on-rails/types type import should occur before import of ./CallbackRegistry.ts

(import/order)

packages/react-on-rails-pro/src/ClientSideRenderer.ts

[error] 28-28: react-on-rails/pageLifecycle import should occur before import of ./StoreRegistry.ts

(import/order)

packages/react-on-rails-pro/src/RSCProvider.tsx

[error] 17-17: react-on-rails/utils import should occur before type import of ./getReactServerComponent.client.ts

(import/order)

packages/react-on-rails-pro/src/registerServerComponent/client.tsx

[error] 18-18: react-on-rails/types import should occur before import of ../RSCRoute.tsx

(import/order)

packages/react-on-rails-pro/src/registerServerComponent/server.tsx

[error] 18-18: react-on-rails/types import should occur before import of ../RSCRoute.tsx

(import/order)

⏰ 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: rspec-package-tests (3.2, minimum)
  • GitHub Check: build
  • GitHub Check: build-dummy-app-webpack-test-bundles (3.4, 22)
  • GitHub Check: rspec-package-tests (3.4, minimum)
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: rspec-package-tests (3.2, latest)
  • GitHub Check: markdown-link-check
  • GitHub Check: build-and-test
🔇 Additional comments (13)
packages/react-on-rails-pro/src/wrapServerComponentRenderer/client.tsx (1)

17-19: LGTM! Import refactoring aligns with package separation architecture.

The migration from relative imports to package-scoped react-on-rails imports correctly implements the dual registry strategy described in the PR objectives, where Pro features layer over core functionality.

packages/react-on-rails-pro/src/CallbackRegistry.ts (1)

15-17: LGTM! Import path migration to package-scoped references.

The migration from relative imports to package-scoped imports (react-on-rails/types, react-on-rails/pageLifecycle, react-on-rails/context) correctly aligns with the Pro package separation architecture where the Pro package depends on Core as a regular dependency.

packages/react-on-rails-pro/src/getReactServerComponent.server.ts (1)

18-19: LGTM! Package-scoped imports for core utilities.

The updated imports for loadJsonFile and type definitions from react-on-rails align with the Pro package architecture and maintain type safety.

packages/react-on-rails-pro/src/ReactOnRailsRSC.ts (1)

110-110: LGTM! Export path updated to package-scoped reference.

Changing the re-export from a relative path (../types/index.ts) to a package-scoped path (react-on-rails/types) correctly reflects the new dependency structure where Pro exports are layered over Core exports.

packages/react-on-rails-pro/src/transformRSCStreamAndReplayConsoleLogs.ts (1)

15-15: LGTM! Type import migrated to package-scoped path.

The update to import RSCPayloadChunk from react-on-rails/types is consistent with the Pro package separation strategy.

packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts (1)

19-24: LGTM! Import paths correctly updated to use core package.

The import paths have been successfully migrated from relative paths to package-scoped paths (react-on-rails/*), aligning with the PR's goal of separating the Pro package from Core. No runtime behavior changes detected.

Also applies to: 33-33

packages/react-on-rails-pro/src/registerServerComponent/server.rsc.ts (1)

15-16: LGTM! Import paths correctly updated to use core package.

The import paths have been cleanly migrated to package-scoped paths, maintaining proper import order. No runtime behavior changes detected.

packages/react-on-rails/src/clientStartup.ts (2)

1-11: LGTM! Clean separation of Core rendering logic.

The simplified implementation correctly delegates all rendering to renderAllComponents() after full page load, which aligns with the Core package's synchronous-only behavior. The comments clearly document the difference between Core and Pro packages.


28-30: Clear documentation of Core vs Pro behavior.

The updated comments effectively communicate that Core waits for full page load while Pro supports immediate hydration. This helps maintainers understand the architectural split.

packages/react-on-rails/src/ClientRenderer.ts (2)

31-60: LGTM! Well-structured renderer delegation pattern.

The delegateToRenderer function cleanly separates the concern of custom renderers with appropriate trace logging. The type casting and early return pattern is clear and correct.


109-164: LGTM! Clean public API surface.

The exported functions provide a clear and consistent API:

  • renderComponent for individual component rendering
  • renderAllStores and renderAllComponents for batch operations
  • reactOnRailsComponentLoaded as a Promise-based wrapper for Pro compatibility

The consistent pattern of checking getRailsContext() and early-returning on null is appropriate.

packages/react-on-rails-pro/src/ComponentRegistry.ts (2)

24-45: LGTM! Solid defensive programming.

The function correctly:

  • Warns on duplicate registrations without blocking (allows hot reload scenarios)
  • Throws on null/undefined components
  • Uses short-circuit evaluation on line 36 to safely check function arity only when renderFunction is true
  • Stores rich metadata for each component

53-54: No action needed on getOrWaitForComponent. Async loading correctly delegates to CallbackRegistry.getOrWaitForItem, and the core package stub exists as intended.

Comment on lines 16 to 19
import ReactOnRails from 'react-on-rails/ReactOnRails.client';
import RSCRoute from '../RSCRoute.tsx';
import { ReactComponentOrRenderFunction } from '../../types/index.ts';
import { ReactComponentOrRenderFunction } from 'react-on-rails/types';
import wrapServerComponentRenderer from '../wrapServerComponentRenderer/client.tsx';
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 | 🟡 Minor

Fix import order per ESLint rules.

The imports are correctly updated to use package-scoped paths, but ESLint is flagging an import order violation. The react-on-rails/types import should come before the relative import ../RSCRoute.tsx.

Apply this diff to fix the import order:

 import * as React from 'react';
 import ReactOnRails from 'react-on-rails/ReactOnRails.client';
+import { ReactComponentOrRenderFunction } from 'react-on-rails/types';
 import RSCRoute from '../RSCRoute.tsx';
-import { ReactComponentOrRenderFunction } from 'react-on-rails/types';
 import wrapServerComponentRenderer from '../wrapServerComponentRenderer/client.tsx';
📝 Committable suggestion

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

Suggested change
import ReactOnRails from 'react-on-rails/ReactOnRails.client';
import RSCRoute from '../RSCRoute.tsx';
import { ReactComponentOrRenderFunction } from '../../types/index.ts';
import { ReactComponentOrRenderFunction } from 'react-on-rails/types';
import wrapServerComponentRenderer from '../wrapServerComponentRenderer/client.tsx';
import * as React from 'react';
import ReactOnRails from 'react-on-rails/ReactOnRails.client';
import { ReactComponentOrRenderFunction } from 'react-on-rails/types';
import RSCRoute from '../RSCRoute.tsx';
import wrapServerComponentRenderer from '../wrapServerComponentRenderer/client.tsx';
🧰 Tools
🪛 ESLint

[error] 18-18: react-on-rails/types import should occur before import of ../RSCRoute.tsx

(import/order)

🤖 Prompt for AI Agents
In packages/react-on-rails-pro/src/registerServerComponent/client.tsx around
lines 16 to 19, the import order violates ESLint: move the package-scoped type
import before the relative RSCRoute import. Reorder so imports are:
ReactOnRails, ReactComponentOrRenderFunction (from 'react-on-rails/types'),
RSCRoute (../RSCRoute.tsx), then wrapServerComponentRenderer; ensure no other
changes to import paths.

import * as React from 'react';
import type { ClientGetReactServerComponentProps } from './getReactServerComponent.client.ts';
import { createRSCPayloadKey } from '../utils.ts';
import { createRSCPayloadKey } from 'react-on-rails/utils';
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 | 🔴 Critical

🧩 Analysis chain

Approve the import path change; fix import order.

The change from a relative import to a package import ('react-on-rails/utils') correctly implements the Pro→Core dependency architecture described in the PR objectives. However, the import order violates ESLint conventions.

Apply this diff to fix the import order:

 import * as React from 'react';
+import { createRSCPayloadKey } from 'react-on-rails/utils';
 import type { ClientGetReactServerComponentProps } from './getReactServerComponent.client.ts';
-import { createRSCPayloadKey } from 'react-on-rails/utils';

Verify that the core package properly exports createRSCPayloadKey via the 'react-on-rails/utils' path:


🏁 Script executed:

#!/bin/bash
# Verify that react-on-rails core package exports createRSCPayloadKey from utils path

# Check package.json exports map in core package
echo "=== Checking core package.json exports map ==="
if [ -f "packages/react-on-rails/package.json" ]; then
  cat packages/react-on-rails/package.json | jq '.exports'
else
  echo "Core package.json not found"
fi

echo ""
echo "=== Searching for createRSCPayloadKey definition in core ==="
# Find where createRSCPayloadKey is defined
rg -n "export.*createRSCPayloadKey" packages/react-on-rails/

echo ""
echo "=== Checking if utils path resolves correctly ==="
# Check if there's a utils file or export in core
fd -t f "utils" packages/react-on-rails/src/

Length of output: 1392


Add missing utils export and fix import order

  1. In packages/react-on-rails/package.json, under exports, add the utils subpath so external imports resolve correctly:
   "exports": {
     ".": { … },
+    "./utils": "./lib/utils.js",
     "./client": "./lib/ReactOnRails.client.js",
     …
   }
  1. In packages/react-on-rails-pro/src/RSCProvider.tsx, reorder the imports to satisfy ESLint:
 import * as React from 'react';
-import type { ClientGetReactServerComponentProps } from './getReactServerComponent.client.ts';
+import { createRSCPayloadKey } from 'react-on-rails/utils';
+import type { ClientGetReactServerComponentProps } from './getReactServerComponent.client.ts';
📝 Committable suggestion

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

Suggested change
import { createRSCPayloadKey } from 'react-on-rails/utils';
// packages/react-on-rails-pro/src/RSCProvider.tsx
import * as React from 'react';
import { createRSCPayloadKey } from 'react-on-rails/utils';
import type { ClientGetReactServerComponentProps } from './getReactServerComponent.client.ts';
// …rest of file…
🧰 Tools
🪛 ESLint

[error] 17-17: react-on-rails/utils import should occur before type import of ./getReactServerComponent.client.ts

(import/order)

Comment on lines 16 to 18
import type { RenderFunction, ReactComponentOrRenderFunction } from 'react-on-rails/types';
import getReactServerComponent from '../getReactServerComponent.server.ts';
import { createRSCProvider } from '../RSCProvider.tsx';
import isRenderFunction from '../../isRenderFunction.ts';
import { assertRailsContextWithServerStreamingCapabilities } from '../../types/index.ts';
import isRenderFunction from 'react-on-rails/isRenderFunction';
import { assertRailsContextWithServerStreamingCapabilities } from 'react-on-rails/types';
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 | 🟡 Minor

Fix import order per ESLint rules.

The migration to module-based imports from the core react-on-rails package aligns with the architectural goal of splitting Pro functionality. However, ESLint correctly flags import order violations: external module imports should be grouped together and precede relative imports.

Apply this diff to fix the import order:

 import * as React from 'react';
 import type { RenderFunction, ReactComponentOrRenderFunction } from 'react-on-rails/types';
+import isRenderFunction from 'react-on-rails/isRenderFunction';
+import { assertRailsContextWithServerStreamingCapabilities } from 'react-on-rails/types';
 import getReactServerComponent from '../getReactServerComponent.server.ts';
 import { createRSCProvider } from '../RSCProvider.tsx';
-import isRenderFunction from 'react-on-rails/isRenderFunction';
-import { assertRailsContextWithServerStreamingCapabilities } from 'react-on-rails/types';
📝 Committable suggestion

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

Suggested change
import type { RenderFunction, ReactComponentOrRenderFunction } from 'react-on-rails/types';
import getReactServerComponent from '../getReactServerComponent.server.ts';
import { createRSCProvider } from '../RSCProvider.tsx';
import isRenderFunction from '../../isRenderFunction.ts';
import { assertRailsContextWithServerStreamingCapabilities } from '../../types/index.ts';
import isRenderFunction from 'react-on-rails/isRenderFunction';
import { assertRailsContextWithServerStreamingCapabilities } from 'react-on-rails/types';
import * as React from 'react';
import type { RenderFunction, ReactComponentOrRenderFunction } from 'react-on-rails/types';
import isRenderFunction from 'react-on-rails/isRenderFunction';
import { assertRailsContextWithServerStreamingCapabilities } from 'react-on-rails/types';
import getReactServerComponent from '../getReactServerComponent.server.ts';
import { createRSCProvider } from '../RSCProvider.tsx';
🧰 Tools
🪛 ESLint

[error] 19-19: react-on-rails/isRenderFunction import should occur before import of ../getReactServerComponent.server.ts

(import/order)


[error] 20-20: react-on-rails/types import should occur before import of ../getReactServerComponent.server.ts

(import/order)

🤖 Prompt for AI Agents
In packages/react-on-rails-pro/src/wrapServerComponentRenderer/server.tsx around
lines 16 to 20, ESLint flags import order violations: move all external package
imports (e.g., imports from 'react-on-rails/*') together and place them before
relative imports (../getReactServerComponent.server.ts, ../RSCProvider.tsx);
reorder the import statements so external modules come first, then relative
imports, and re-run the linter to confirm the fix.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify that core package exports these symbols at the specified paths.

Ensure the core react-on-rails package exports RenderFunction, ReactComponentOrRenderFunction, assertRailsContextWithServerStreamingCapabilities from react-on-rails/types, and isRenderFunction from react-on-rails/isRenderFunction.

Run the following script to verify the exports exist in the core package:


🏁 Script executed:

#!/bin/bash
# Description: Verify that the core package exports the required symbols

echo "=== Checking react-on-rails/types exports ==="
# Look for RenderFunction, ReactComponentOrRenderFunction, and assertRailsContextWithServerStreamingCapabilities
ast-grep --pattern $'export type RenderFunction = $$$'
ast-grep --pattern $'export type ReactComponentOrRenderFunction = $$$'
ast-grep --pattern $'export $$$assertRailsContextWithServerStreamingCapabilities$$$'

echo ""
echo "=== Checking react-on-rails/isRenderFunction export ==="
# Look for isRenderFunction export
fd -t f 'isRenderFunction' packages/react-on-rails --exec rg -n "export.*isRenderFunction" {}

echo ""
echo "=== Checking package.json exports field ==="
# Verify package.json exports configuration
cat packages/react-on-rails/package.json | jq '.exports'

Length of output: 1100


🏁 Script executed:

#!/bin/bash
# Locate types definition files in core package
echo "=== Locating types definition files ==="
fd -t f -e ts -e d.ts types* packages/react-on-rails

# Inspect definitions for RenderFunction and related types
echo ""
echo "=== Checking definitions ==="
rg -n "export.*RenderFunction" -C2 packages/react-on-rails
rg -n "export.*ReactComponentOrRenderFunction" -C2 packages/react-on-rails
rg -n "export.*assertRailsContextWithServerStreamingCapabilities" -C2 packages/react-on-rails

Length of output: 878


Add missing exports for types and isRenderFunction in package.json

The core package’s exports field currently lacks entries for both types and isRenderFunction, so these imports will fail at runtime:

  • You must expose react-on-rails/types (where RenderFunction, ReactComponentOrRenderFunction, and assertRailsContextWithServerStreamingCapabilities live).
  • You must expose react-on-rails/isRenderFunction (the default export in src/isRenderFunction.ts).

Update packages/react-on-rails/package.json’s exports to include something like:

{
  "exports": {
    "./types": {
      "import": "./lib/types/index.d.ts",
      "require": "./lib/types/index.d.ts"
    },
    "./isRenderFunction": {
      "import": "./lib/isRenderFunction.js",
      "require": "./lib/isRenderFunction.js"
    },
    ...
  }
}

This ensures your imports in wrapServerComponentRenderer/server.tsx resolve correctly.

🧰 Tools
🪛 ESLint

[error] 19-19: react-on-rails/isRenderFunction import should occur before import of ../getReactServerComponent.server.ts

(import/order)


[error] 20-20: react-on-rails/types import should occur before import of ../getReactServerComponent.server.ts

(import/order)

🤖 Prompt for AI Agents
In packages/react-on-rails-pro/src/wrapServerComponentRenderer/server.tsx around
lines 16 to 20, imports from 'react-on-rails/types' and
'react-on-rails/isRenderFunction' will fail because the core package's
package.json exports lack entries for these paths; update
packages/react-on-rails/package.json to add exports for "./types" pointing to
the built types (e.g., lib/types/index.d.ts) and for "./isRenderFunction"
pointing to the built JS (e.g., lib/isRenderFunction.js) for both "import" and
"require" consumers so those imports resolve at runtime.

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

🧹 Nitpick comments (1)
packages/react-on-rails-pro/src/index.ts (1)

145-150: Consider simplifying the double guard pattern.

The guard uses both a check (line 145) and a setter (line 150) for the same global flag. While defensive, this pattern is slightly redundant since the check alone would prevent re-execution.

Optional simplification:

-  // eslint-disable-next-line no-underscore-dangle
-  if (globalThis.__REACT_ON_RAILS_EVENT_HANDLERS_RAN_ONCE__) {
-    return;
-  }
-
-  // eslint-disable-next-line no-underscore-dangle
-  globalThis.__REACT_ON_RAILS_EVENT_HANDLERS_RAN_ONCE__ = true;
+  if (globalThis.__REACT_ON_RAILS_EVENT_HANDLERS_RAN_ONCE__) return;
+  globalThis.__REACT_ON_RAILS_EVENT_HANDLERS_RAN_ONCE__ = true;

However, the current implementation is acceptable for clarity.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 80a1220 and 1e05f49.

📒 Files selected for processing (13)
  • .gitignore (1 hunks)
  • CHANGELOG.md (1 hunks)
  • LICENSE.md (1 hunks)
  • docs/JS_PRO_PACKAGE_SEPARATION_PLAN.md (1 hunks)
  • package.json (2 hunks)
  • packages/react-on-rails-pro/package.json (1 hunks)
  • packages/react-on-rails-pro/src/RSCRoute.tsx (1 hunks)
  • packages/react-on-rails-pro/src/index.ts (1 hunks)
  • packages/react-on-rails-pro/tsconfig.json (1 hunks)
  • packages/react-on-rails/package.json (3 hunks)
  • packages/react-on-rails/tests/serverRenderReactComponent.test.ts (2 hunks)
  • packages/react-on-rails/tsconfig.json (1 hunks)
  • tsconfig.json (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • packages/react-on-rails/tsconfig.json
  • LICENSE.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/react-on-rails-pro/tsconfig.json
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Prettier is the sole authority for formatting all non-Ruby files; never manually format them

Files:

  • package.json
  • docs/JS_PRO_PACKAGE_SEPARATION_PLAN.md
  • CHANGELOG.md
  • packages/react-on-rails/tests/serverRenderReactComponent.test.ts
  • packages/react-on-rails-pro/src/RSCRoute.tsx
  • packages/react-on-rails/package.json
  • tsconfig.json
  • packages/react-on-rails-pro/package.json
  • packages/react-on-rails-pro/src/index.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for JS/TS code (lint via rake lint or yarn lint)

Files:

  • packages/react-on-rails/tests/serverRenderReactComponent.test.ts
  • packages/react-on-rails-pro/src/RSCRoute.tsx
  • packages/react-on-rails-pro/src/index.ts
🧠 Learnings (4)
📚 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
  • packages/react-on-rails-pro/src/index.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:

  • packages/react-on-rails/tests/serverRenderReactComponent.test.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:

  • packages/react-on-rails/package.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:

  • packages/react-on-rails-pro/src/index.ts
🧬 Code graph analysis (1)
packages/react-on-rails-pro/src/index.ts (4)
packages/react-on-rails/src/types/index.ts (3)
  • ReactComponentOrRenderFunction (178-178)
  • StoreGenerator (184-184)
  • Store (183-183)
packages/react-on-rails-pro/src/ClientSideRenderer.ts (7)
  • renderOrHydrateComponent (257-266)
  • hydrateStore (293-312)
  • hydrateAllStores (317-318)
  • renderOrHydrateAllComponents (282-283)
  • unmountAll (325-328)
  • renderOrHydrateImmediateHydratedComponents (276-280)
  • hydrateImmediateHydratedStores (314-315)
packages/react-on-rails/src/turbolinksUtils.ts (1)
  • debugTurbolinks (14-22)
packages/react-on-rails/src/pageLifecycle.ts (2)
  • onPageLoaded (77-83)
  • onPageUnloaded (85-91)
🪛 ESLint
packages/react-on-rails-pro/src/index.ts

[error] 34-39: react-on-rails/types type import should occur before import of ./ComponentRegistry.ts

(import/order)


[error] 124-124: react-on-rails/pageLifecycle import should occur before import of ./ComponentRegistry.ts

(import/order)


[error] 125-125: react-on-rails/turbolinksUtils import should occur before import of ./ComponentRegistry.ts

(import/order)

🪛 GitHub Actions: Lint JS and Ruby
package.json

[warning] 1-1: Configuration hints: Unused item in ignoreDependencies (workspace: spec/dummy): @babel/runtime

packages/react-on-rails-pro/src/RSCRoute.tsx

[warning] 1-1: Unused files detected by knip.

packages/react-on-rails/package.json

[warning] 1-1: Configuration hints: Unused item in ignoreDependencies (workspace: spec/dummy): @babel/runtime

packages/react-on-rails-pro/package.json

[warning] 1-1: Configuration hints: Unused item in ignoreDependencies (workspace: spec/dummy): @babel/runtime

⏰ 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). (6)
  • GitHub Check: rspec-package-tests (3.2, latest)
  • GitHub Check: rspec-package-tests (3.4, minimum)
  • GitHub Check: markdown-link-check
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: rspec-package-tests (3.2, minimum)
  • GitHub Check: build-and-test
🔇 Additional comments (20)
packages/react-on-rails-pro/src/RSCRoute.tsx (1)

74-78: LGTM! Pragmatic workaround for React.use typing.

The explicit cast to any for React.use is a reasonable workaround for React 18.3+ where the TypeScript types may not yet fully support this experimental API. The comment and scoped eslint-disable are appropriate.

.gitignore (1)

25-32: LGTM! Appropriate ignore rules for TypeScript build artifacts.

The new ignore patterns correctly exclude TypeScript compilation artifacts from source directories while preserving test files. The rules are well-scoped and align with the monorepo structure.

tsconfig.json (1)

6-19: LGTM! Improved TypeScript configuration for monorepo.

The addition of declaration, declarationMap, and sourceMap correctly enables type definitions and debugging support. Removing outDir and include from the root config is the right approach when delegating to per-package configurations.

packages/react-on-rails/package.json (4)

8-8: LGTM! Build script simplified appropriately.

Removing the --declaration flag is correct since declaration generation is now configured in tsconfig.json (lines 6-7).


42-58: LGTM! Export map reflects package separation.

The new exports expose granular internal modules for advanced usage, while legacy Pro server/component exports have been correctly removed in favor of the new react-on-rails-pro package.


71-72: LGTM! Files field correctly includes TypeScript declarations.

Expanding the files field to include both .js and .d.ts files ensures TypeScript consumers receive type definitions.


63-63: Approve peerDependency: react-on-rails-rsc@19.0.2 exists
react-on-rails-rsc@19.0.2 is published on npm and listed on Libraries.io, so the peerDependency version is valid.

CHANGELOG.md (1)

74-74: LGTM! Documentation accurately reflects package separation.

The updated wording correctly describes the new Pro package structure as packages/react-on-rails-pro/ rather than the old directory-based separation.

package.json (2)

8-9: LGTM! Workspace configuration updated correctly.

The addition of packages/react-on-rails-pro to the workspaces array properly integrates the new Pro package into the monorepo structure.


65-65: LGTM! Build order ensures correct dependency resolution.

The sequential build (react-on-rails first, then react-on-rails-pro) correctly enforces the dependency relationship where Pro depends on core. This prevents build failures from missing core artifacts.

packages/react-on-rails-pro/package.json (4)

46-47: LGTM! Core dependency uses caret range as planned.

The ^16.1.0 caret range for react-on-rails correctly follows the versioning strategy outlined in the implementation plan (lines 17-22), allowing minor and patch updates while requiring major version alignment.


30-45: LGTM! Exports map properly structured for RSC support.

The conditional exports using "react-server" and "default" correctly implement dual-entry RSC support for server component registration and wrapping. The structure aligns with React Server Components conventions.


54-58: LGTM! Optional peer dependency correctly configured.

Marking react-on-rails-rsc as optional in peerDependenciesMeta properly supports both RSC and non-RSC use cases, allowing Pro users to opt into RSC features without forcing the dependency.


52-52: No update required for react-on-rails-rsc version: Verified that version 19.0.2 is published as the latest release on npm.

packages/react-on-rails-pro/src/index.ts (6)

42-118: LGTM! Pro wrapper correctly augments core with enhanced registries.

The ReactOnRailsPro object properly:

  • Spreads all core functionality (line 43)
  • Overrides registration methods to route through Pro registries (lines 46-63)
  • Adds async retrieval methods (getOrWaitForComponent, getOrWaitForStore) that are Pro-exclusive
  • Exposes hydration control methods (reactOnRailsComponentLoaded, reactOnRailsStoreLoaded)

This design follows the "layer Pro over core" strategy outlined in the implementation plan.


121-121: LGTM! Global replacement enables Pro features transparently.

Replacing globalThis.ReactOnRails with the Pro version ensures all code accessing the global API gets Pro-enhanced behavior, including async registry methods and immediate hydration support.


127-131: LGTM! Pro page load handler implements parallel hydration.

The use of Promise.all correctly hydrates stores and components in parallel for optimal performance. The debug logging aids troubleshooting.


133-136: LGTM! Private function correctly kept internal.

The reactOnRailsPageUnloaded function is intentionally not exported, as it's only used internally as a callback for onPageUnloaded (line 158). This aligns with the established pattern in the codebase.

Based on learnings


152-159: LGTM! Pro startup correctly implements immediate hydration.

The startup sequence properly:

  1. Hydrates immediate-hydration components/stores before page load (lines 153-154)
  2. Registers handlers for full page load and unload (lines 157-158)
  3. Guards against re-execution (lines 145-150)

The void operator correctly handles Promise returns when not awaiting. This implements the Pro feature set as designed.

Based on learnings


162-162: LGTM! Immediate startup execution appropriate for entry point.

Calling clientStartup() at module load time ensures Pro hydration behavior is active as soon as the package is imported. This is the correct pattern for a side-effect entry point.

import { onPageLoaded, onPageUnloaded } from 'react-on-rails/pageLifecycle';
import { debugTurbolinks } from 'react-on-rails/turbolinksUtils';
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 | 🟡 Minor

Fix import order for page lifecycle utilities.

Move these imports before the relative module imports (lines 22-32) to comply with ESLint import ordering rules.

Apply this diff:

 // Import core ReactOnRails to enhance it
 import ReactOnRailsCore from 'react-on-rails/ReactOnRails.client';
+import { onPageLoaded, onPageUnloaded } from 'react-on-rails/pageLifecycle';
+import { debugTurbolinks } from 'react-on-rails/turbolinksUtils';

 // Import pro registries and features
 import * as ProComponentRegistry from './ComponentRegistry.ts';

And remove the duplicate imports from line 124-125.

📝 Committable suggestion

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

Suggested change
import { onPageLoaded, onPageUnloaded } from 'react-on-rails/pageLifecycle';
import { debugTurbolinks } from 'react-on-rails/turbolinksUtils';
// Import core ReactOnRails to enhance it
import ReactOnRailsCore from 'react-on-rails/ReactOnRails.client';
import { onPageLoaded, onPageUnloaded } from 'react-on-rails/pageLifecycle';
import { debugTurbolinks } from 'react-on-rails/turbolinksUtils';
// Import pro registries and features
import * as ProComponentRegistry from './ComponentRegistry.ts';
🧰 Tools
🪛 ESLint

[error] 124-124: react-on-rails/pageLifecycle import should occur before import of ./ComponentRegistry.ts

(import/order)


[error] 125-125: react-on-rails/turbolinksUtils import should occur before import of ./ComponentRegistry.ts

(import/order)

🤖 Prompt for AI Agents
In packages/react-on-rails-pro/src/index.ts (current duplicate imports at lines
124-125), the named imports from 'react-on-rails/pageLifecycle' and
'react-on-rails/turbolinksUtils' must be moved to precede the relative module
imports around lines 22-32 to satisfy ESLint import ordering; remove the
duplicate import lines at 124-125 and reinsert those two import statements just
above the block of relative imports at lines 22-32 so all external package
imports appear before local/relative imports.

Comment on lines 29 to 31
// Setup globalThis.ReactOnRails for serverRenderReactComponent
globalThis.ReactOnRails = { getComponent: ComponentRegistry.get } as any;
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 | 🔴 Critical

Avoid unbound registry method in global setup.

Assigning getComponent: ComponentRegistry.get loses the class context, so inside get any use of this (e.g., this.components()) now points at globalThis.ReactOnRails, which lacks the registry map. Calls like ComponentRegistry.get('X1') will throw because this.components is undefined. Bind the method to ComponentRegistry (e.g., ComponentRegistry.get.bind(ComponentRegistry)) or expose globalThis.ReactOnRails from the module itself so the method retains its original context.

🤖 Prompt for AI Agents
In packages/react-on-rails/tests/serverRenderReactComponent.test.ts around lines
29-30, the test global setup assigns getComponent directly from
ComponentRegistry which loses its class context causing this.components to be
undefined; fix by binding the method to ComponentRegistry (e.g., use
ComponentRegistry.get.bind(ComponentRegistry)) or otherwise expose the module
instance so getComponent retains ComponentRegistry as its `this` before
assigning it to globalThis.ReactOnRails.

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: 5

Caution

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

⚠️ Outside diff range comments (1)
packages/react-on-rails-pro/tests/registerServerComponent.client.test.jsx (1)

8-8: Fix missing @testing-library/react dependency.

The pipeline is failing because the @testing-library/react module cannot be found. This dependency needs to be added to the react-on-rails-pro package.json.

Ensure @testing-library/react is added to the devDependencies in packages/react-on-rails-pro/package.json:

{
  "devDependencies": {
    "@testing-library/react": "^14.0.0"
  }
}
♻️ Duplicate comments (1)
packages/react-on-rails-pro/src/getReactServerComponent.client.ts (1)

17-19: Fix import order per ESLint rules.

The package-scoped import from react-on-rails/types at line 19 should come before the relative import at line 17 to satisfy ESLint's import ordering rule.

Apply this diff to fix the import order:

 import * as React from 'react';
 import { createFromReadableStream } from 'react-on-rails-rsc/client.browser';
-import { createRSCPayloadKey, fetch, wrapInNewPromise, extractErrorMessage } from './utils.ts';
+import { RailsContext } from 'react-on-rails/types';
 import transformRSCStreamAndReplayConsoleLogs from './transformRSCStreamAndReplayConsoleLogs.ts';
-import { RailsContext } from 'react-on-rails/types';
+import { createRSCPayloadKey, fetch, wrapInNewPromise, extractErrorMessage } from './utils.ts';
🧹 Nitpick comments (6)
packages/react-on-rails-pro/src/injectRSCPayload.ts (1)

17-18: Fix import order per ESLint convention.

Package imports should come before local relative imports. Swap the order of these two lines.

Apply this diff:

-import { createRSCPayloadKey } from './utils.ts';
 import { PipeableOrReadableStream } from 'react-on-rails/types';
+import { createRSCPayloadKey } from './utils.ts';
packages/react-on-rails-pro/src/RSCRequestTracker.ts (1)

16-21: Fix import order per ESLint convention.

Package imports should come before local relative imports. Move the react-on-rails/types import above the local ./utils.ts import.

Apply this diff:

-import { extractErrorMessage } from './utils.ts';
 import {
   RSCPayloadStreamInfo,
   RSCPayloadCallback,
   RailsContextWithServerComponentMetadata,
 } from 'react-on-rails/types';
+import { extractErrorMessage } from './utils.ts';
packages/react-on-rails-pro/src/utils.ts (1)

1-6: Consider removing the unnecessary fetch wrapper.

The customFetch function simply delegates to the global fetch without adding any value. Unless there's a specific reason for this wrapper (e.g., future instrumentation, testing hooks), it's cleaner to directly export the global fetch or omit this utility altogether.

If there's no future need for customization, remove this wrapper:

-const customFetch = (...args: Parameters<typeof fetch>) => {
-  const res = fetch(...args);
-  return res;
-};
-
-export { customFetch as fetch };
packages/react-on-rails-pro/tests/jest.setup.js (1)

4-11: Consider warnings instead of throwing errors for polyfill detection.

Throwing errors when polyfills are already defined will break all tests, which may be overly disruptive during environment upgrades. Consider using console.warn() instead to alert developers without blocking test execution.

Apply this diff:

-if (typeof window !== 'undefined' && typeof window.TextEncoder !== 'undefined') {
-  throw new Error('TextEncoder is already defined, remove the polyfill');
-}
+if (typeof window !== 'undefined' && typeof window.TextEncoder !== 'undefined') {
+  console.warn('TextEncoder is already defined, consider removing the polyfill');
+}

-if (typeof window !== 'undefined' && typeof window.MessageChannel !== 'undefined') {
-  throw new Error('MessageChannel is already defined, remove the polyfill');
-}
+if (typeof window !== 'undefined' && typeof window.MessageChannel !== 'undefined') {
+  console.warn('MessageChannel is already defined, consider removing the polyfill');
+}
packages/react-on-rails-pro/src/loadJsonFile.ts (2)

18-19: Consider cache invalidation strategy.

The module-scoped cache persists indefinitely without invalidation. In production this is fine for immutable manifest files, but in development or hot-reload scenarios, stale cached values could cause issues if manifest files are regenerated.

If hot-reload support is needed, consider adding a cache-clearing mechanism:

// Optional: Export for testing/hot-reload scenarios
export function clearCache() {
  loadedJsonFiles.clear();
}

27-31: Type assertion assumes consistent generic usage per file.

The cache lookup on line 28-30 uses a type assertion as T without validating that the cached object matches the requested generic type. If callers request different types for the same file path, the assertion could be unsafe.

This is acceptable given the narrow use case (manifest files with known structure), but for stricter type safety, consider storing the type information in the cache or validating at runtime.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e05f49 and 85269e8.

📒 Files selected for processing (13)
  • packages/react-on-rails-pro/jest.config.js (1 hunks)
  • packages/react-on-rails-pro/src/RSCProvider.tsx (1 hunks)
  • packages/react-on-rails-pro/src/RSCRequestTracker.ts (1 hunks)
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts (2 hunks)
  • packages/react-on-rails-pro/src/getReactServerComponent.client.ts (1 hunks)
  • packages/react-on-rails-pro/src/getReactServerComponent.server.ts (1 hunks)
  • packages/react-on-rails-pro/src/injectRSCPayload.ts (1 hunks)
  • packages/react-on-rails-pro/src/loadJsonFile.ts (1 hunks)
  • packages/react-on-rails-pro/src/utils.ts (1 hunks)
  • packages/react-on-rails-pro/tests/jest.setup.js (1 hunks)
  • packages/react-on-rails-pro/tests/registerServerComponent.client.test.jsx (1 hunks)
  • packages/react-on-rails-pro/tests/streamServerRenderedReactComponent.test.jsx (1 hunks)
  • packages/react-on-rails/package.json (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts
  • packages/react-on-rails-pro/src/RSCProvider.tsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Prettier is the sole authority for formatting all non-Ruby files; never manually format them

Files:

  • packages/react-on-rails-pro/src/injectRSCPayload.ts
  • packages/react-on-rails-pro/src/loadJsonFile.ts
  • packages/react-on-rails-pro/jest.config.js
  • packages/react-on-rails-pro/src/utils.ts
  • packages/react-on-rails-pro/tests/streamServerRenderedReactComponent.test.jsx
  • packages/react-on-rails-pro/tests/jest.setup.js
  • packages/react-on-rails/package.json
  • packages/react-on-rails-pro/src/getReactServerComponent.client.ts
  • packages/react-on-rails-pro/src/RSCRequestTracker.ts
  • packages/react-on-rails-pro/src/getReactServerComponent.server.ts
  • packages/react-on-rails-pro/tests/registerServerComponent.client.test.jsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for JS/TS code (lint via rake lint or yarn lint)

Files:

  • packages/react-on-rails-pro/src/injectRSCPayload.ts
  • packages/react-on-rails-pro/src/loadJsonFile.ts
  • packages/react-on-rails-pro/jest.config.js
  • packages/react-on-rails-pro/src/utils.ts
  • packages/react-on-rails-pro/tests/streamServerRenderedReactComponent.test.jsx
  • packages/react-on-rails-pro/tests/jest.setup.js
  • packages/react-on-rails-pro/src/getReactServerComponent.client.ts
  • packages/react-on-rails-pro/src/RSCRequestTracker.ts
  • packages/react-on-rails-pro/src/getReactServerComponent.server.ts
  • packages/react-on-rails-pro/tests/registerServerComponent.client.test.jsx
🧠 Learnings (5)
📚 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:

  • packages/react-on-rails-pro/tests/streamServerRenderedReactComponent.test.jsx
📚 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:

  • packages/react-on-rails/package.json
📚 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:

  • packages/react-on-rails-pro/src/getReactServerComponent.client.ts
  • packages/react-on-rails-pro/src/getReactServerComponent.server.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:

  • packages/react-on-rails-pro/src/getReactServerComponent.client.ts
  • packages/react-on-rails-pro/src/RSCRequestTracker.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:

  • packages/react-on-rails-pro/src/getReactServerComponent.client.ts
  • packages/react-on-rails-pro/src/RSCRequestTracker.ts
🪛 ESLint
packages/react-on-rails-pro/src/injectRSCPayload.ts

[error] 18-18: react-on-rails/types import should occur before import of ./utils.ts

(import/order)

packages/react-on-rails-pro/src/getReactServerComponent.client.ts

[error] 19-19: react-on-rails/types import should occur before import of ./utils.ts

(import/order)

packages/react-on-rails-pro/src/RSCRequestTracker.ts

[error] 17-21: react-on-rails/types import should occur before import of ./utils.ts

(import/order)

packages/react-on-rails-pro/src/getReactServerComponent.server.ts

[error] 19-19: react-on-rails/types type import should occur before import of ./transformRSCNodeStream.ts

(import/order)

packages/react-on-rails-pro/tests/registerServerComponent.client.test.jsx

[error] 12-12: Relative import from another package is not allowed. Use react-on-rails/tests/testUtils.js instead of ../../react-on-rails/tests/testUtils.js

(import/no-relative-packages)

🪛 GitHub Actions: JS unit tests for Renderer package
packages/react-on-rails-pro/tests/registerServerComponent.client.test.jsx

[error] 8-8: Cannot find module '@testing-library/react' from 'tests/registerServerComponent.client.test.jsx'.

⏰ 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: build
  • GitHub Check: rspec-package-tests (3.2, latest)
  • GitHub Check: rspec-package-tests (3.4, minimum)
  • GitHub Check: rspec-package-tests (3.2, minimum)
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: markdown-link-check
  • GitHub Check: build-and-test
🔇 Additional comments (11)
packages/react-on-rails-pro/tests/streamServerRenderedReactComponent.test.jsx (2)

11-353: LGTM! Test logic remains unchanged.

The test implementation is unchanged, which is appropriate for this refactoring. All test cases continue to validate streaming functionality, error handling, and various component types without modification.


7-9: Import paths verified — All referenced modules (streamServerRenderedReactComponent.ts, ComponentRegistry.ts, index.ts) are present under packages/react-on-rails-pro/src, so the updated imports resolve correctly.

packages/react-on-rails-pro/tests/registerServerComponent.client.test.jsx (1)

13-15: LGTM! Import path updates align with new package structure.

The internal imports have been correctly updated to reflect the new package structure where Pro package modules are reorganized under src/.

packages/react-on-rails/package.json (2)

36-58: LGTM! Comprehensive subpath exports configured.

The exports map properly exposes all necessary subpaths (types, utils, context, etc.) that the Pro package depends on. This aligns with the package separation architecture.


69-72: LGTM! TypeScript declaration files properly included.

The files field update ensures both JavaScript and TypeScript declaration files are distributed, which is essential for TypeScript consumers of this package.

packages/react-on-rails-pro/jest.config.js (1)

1-31: LGTM! Well-structured package-specific Jest configuration.

The configuration correctly:

  • Extends the root base config for consistency
  • Adds package-specific test matching and setup
  • Conditionally mocks RSC modules for Node versions < 18
  • Sets appropriate rootDir for the package

This follows monorepo best practices for package-level test configurations.

packages/react-on-rails-pro/src/utils.ts (3)

8-10: Consider potential JSON.stringify issues with circular references or large objects.

The createRSCPayloadKey function uses JSON.stringify on componentProps, which could throw an error for objects with circular references or be inefficient for large props objects. Consider adding error handling or using a safer stringification method if props can be complex.

Verify whether componentProps can contain circular references or very large objects. If so, consider using a library like fast-safe-stringify or implementing a custom stringification with depth limits.


12-17: Clarify the purpose of wrapping promises.

The wrapInNewPromise function wraps a promise in a new promise without apparent benefit. This pattern is typically used to break promise chains or ensure promise identity changes, but the use case isn't clear from the code.

Please document why this wrapper is needed. If it's not required for a specific architectural reason (e.g., breaking circular references, ensuring new promise identity), consider removing it to reduce complexity.


19-21: LGTM! Clean error message extraction.

The extractErrorMessage function properly handles both Error instances and other thrown values, providing a safe way to extract error messages.

packages/react-on-rails-pro/tests/jest.setup.js (1)

43-64: LGTM! Polyfill implementation is sound.

The global assignments and MessageChannel mock implementation correctly polyfill browser APIs for Jest's jsdom environment. The MessageChannel implementation with port1/port2 and onmessage/postMessage semantics aligns with the browser API.

packages/react-on-rails-pro/src/loadJsonFile.ts (1)

33-40: LGTM! Error handling is appropriate.

The try-catch block correctly logs the file path for debugging and re-throws the error to propagate failures upstream. This is appropriate for server-side manifest loading where errors should halt execution.

Comment on lines +26 to +41
jest.mock('../src/utils', () => ({
...jest.requireActual('../src/utils'),
fetch: (...args) =>
jest
.requireActual('../src/utils')
.fetch(...args)
.then((res) => {
const originalBody = res.body;
if (originalBody instanceof Readable) {
Object.defineProperty(res, 'body', {
value: Readable.toWeb(originalBody),
});
}
return res;
}),
}));
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 | 🔴 Critical

🧩 Analysis chain

Verify the fetch mock implementation and path resolution.

The jest.mock('../src/utils') call uses a relative path that should resolve correctly from this setup file. However, ensure that:

  1. The path ../src/utils correctly points to the utils module from this setup file's location.
  2. Readable.toWeb() is the correct Node.js API for converting Node Readable streams to web ReadableStreams (this API was added in Node.js v16.5.0).

Run the following script to verify the utils module path and check Node.js version requirements:


🏁 Script executed:

#!/bin/bash
# Verify the utils module exists at the expected path
fd -t f "utils\.(ts|js)$" packages/react-on-rails-pro/src

# Check if there are any references to Readable.toWeb in the codebase or docs
rg -n "Readable\.toWeb" 

# Verify package.json specifies compatible Node.js version (>=16.5.0 for Readable.toWeb)
fd -t f "package\.json$" packages/react-on-rails-pro --exec cat {}

Length of output: 2274


Enforce Node.js ≥16.5.0 in package.json

Add an "engines" field to packages/react-on-rails-pro/package.json to require Node.js v16.5.0 or newer (needed for Readable.toWeb):

"engines": {
  "node": ">=16.5.0"
}
🤖 Prompt for AI Agents
In packages/react-on-rails-pro/tests/jest.setup.js around lines 26 to 41, the
test uses Readable.toWeb which requires Node.js >=16.5.0; update
packages/react-on-rails-pro/package.json to add an "engines" field requiring
Node >=16.5.0 (e.g., "node": ">=16.5.0") so CI and developers are prevented from
running tests on older Node versions; commit the package.json change and update
lockfiles if necessary.

Comment on lines +66 to +76
if (!['yes', 'true', 'y', 't'].includes(process.env.ENABLE_JEST_CONSOLE || ''.toLowerCase())) {
global.console.log('All calls to console have been disabled in jest.setup.js');

global.console = {
log: jest.fn(),
error: jest.fn(),
warn: jest.fn(),
info: jest.fn(),
debug: jest.fn(),
};
}
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 | 🟡 Minor

Incomplete console mock may cause runtime errors.

The console mock only implements five methods (log, error, warn, info, debug). If test code or dependencies call other console methods like console.table(), console.trace(), console.assert(), etc., it will result in "undefined is not a function" errors.

Apply this diff to preserve unmocked console methods:

  global.console = {
+   ...global.console,
    log: jest.fn(),
    error: jest.fn(),
    warn: jest.fn(),
    info: jest.fn(),
    debug: jest.fn(),
  };

This preserves other console methods while mocking the commonly used ones.

📝 Committable suggestion

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

Suggested change
if (!['yes', 'true', 'y', 't'].includes(process.env.ENABLE_JEST_CONSOLE || ''.toLowerCase())) {
global.console.log('All calls to console have been disabled in jest.setup.js');
global.console = {
log: jest.fn(),
error: jest.fn(),
warn: jest.fn(),
info: jest.fn(),
debug: jest.fn(),
};
}
if (!['yes', 'true', 'y', 't'].includes(process.env.ENABLE_JEST_CONSOLE || ''.toLowerCase())) {
global.console.log('All calls to console have been disabled in jest.setup.js');
global.console = {
...global.console,
log: jest.fn(),
error: jest.fn(),
warn: jest.fn(),
info: jest.fn(),
debug: jest.fn(),
};
}
🤖 Prompt for AI Agents
In packages/react-on-rails-pro/tests/jest.setup.js around lines 66 to 76, the
current console mock replaces the global console with only five methods which
can break tests that call other console methods; instead, preserve the original
console methods and only override the common ones (log, error, warn, info,
debug) with jest.fn() so calls like console.table/trace/assert still exist —
capture the original global.console, create a new console object that
spreads/merges the original console and overwrites the five methods with
jest.fn(), and assign that back to global.console (keeping the
ENABLE_JEST_CONSOLE check as-is).

import ReactOnRails from '../src/ReactOnRails.client.ts';
import registerServerComponent from '../src/pro/registerServerComponent/client.tsx';
import { clear as clearComponentRegistry } from '../src/pro/ComponentRegistry.ts';
import { createNodeReadableStream, getNodeVersion } from '../../react-on-rails/tests/testUtils.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 | 🔴 Critical

Use package-scoped import instead of relative path.

Cross-package relative imports violate the ESLint rule import/no-relative-packages. Since this is importing from the core react-on-rails package, use the package name instead.

Apply this diff:

-import { createNodeReadableStream, getNodeVersion } from '../../react-on-rails/tests/testUtils.js';
+import { createNodeReadableStream, getNodeVersion } from 'react-on-rails/tests/testUtils.js';
📝 Committable suggestion

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

Suggested change
import { createNodeReadableStream, getNodeVersion } from '../../react-on-rails/tests/testUtils.js';
// packages/react-on-rails-pro/tests/registerServerComponent.client.test.jsx
import { createNodeReadableStream, getNodeVersion } from 'react-on-rails/tests/testUtils.js';
🧰 Tools
🪛 ESLint

[error] 12-12: Relative import from another package is not allowed. Use react-on-rails/tests/testUtils.js instead of ../../react-on-rails/tests/testUtils.js

(import/no-relative-packages)

🤖 Prompt for AI Agents
In packages/react-on-rails-pro/tests/registerServerComponent.client.test.jsx
around line 12, the test imports from a cross-package relative path which
violates import/no-relative-packages; replace the relative import
"../../react-on-rails/tests/testUtils.js" with the package-scoped import
"react-on-rails/tests/testUtils.js" (i.e. import { createNodeReadableStream,
getNodeVersion } from 'react-on-rails/tests/testUtils.js';) so the import uses
the package name rather than a relative path.

@claude
Copy link

claude bot commented Oct 2, 2025

Comprehensive Code Review: PR #1841

Executive Summary

This PR implements a major architectural change to separate Pro JavaScript functionality from the core React-on-Rails package into a new react-on-rails-pro package. The implementation is well-planned and mostly well-executed, with a comprehensive implementation plan document and systematic checkpoint-based approach.

Overall Assessment: ⚠️ CHANGES REQUESTED

Key Statistics:

  • Files Changed: 54 files
  • Additions: 1,705 lines
  • Deletions: 193 lines
  • Overall Score: 4.1/5 ⭐⭐⭐⭐

Critical Issues That Must Be Fixed 🔴

1. Code Duplication Violates DRY Principle

Files: packages/react-on-rails-pro/src/utils.ts, packages/react-on-rails-pro/src/loadJsonFile.ts

These utilities are duplicated from the core package, violating the stated "Code Reuse Strategy" in your implementation plan (Section 4).

Action Required:

  • Export these utilities from core package
  • Import them in pro package: import { createRSCPayloadKey } from 'react-on-rails/utils'
  • Remove duplicated code

2. Missing Core Package Exports

File: packages/react-on-rails/package.json

The core package needs to export modules that the pro package imports. Currently missing:

Required exports:

"exports": {
  ".": { ... },
  "./client": "./lib/ReactOnRails.client.js",
  "./types": "./lib/types/index.js",
  "./utils": "./lib/utils.js",
  "./context": "./lib/context.js",
  "./createReactOutput": "./lib/createReactOutput.js",
  "./pageLifecycle": "./lib/pageLifecycle.js",
  "./reactApis": "./lib/reactApis.cjs",
  "./isRenderFunction": "./lib/isRenderFunction.js",
  "./handleError": "./lib/handleError.js",
  "./serverRenderUtils": "./lib/serverRenderUtils.js",
  "./buildConsoleReplay": "./lib/buildConsoleReplay.js",
  "./ReactDOMServer": "./lib/ReactDOMServer.cjs",
  "./isServerRenderResult": "./lib/isServerRenderResult.js",
  "./turbolinksUtils": "./lib/turbolinksUtils.js"
}

3. Incorrect peerDependencies Configuration

File: packages/react-on-rails/package.json

The core package declares react-on-rails-rsc as a peerDependency, but this is Pro-only.

Action Required:

  • Move react-on-rails-rsc peerDependency to pro package only
  • Core package should only have react and react-dom

4. Missing NOTICE File

Action Required:

  • Add /packages/react-on-rails-pro/NOTICE referencing the Pro license

5. Incomplete User Documentation

Missing:

  • Pro package README with installation instructions
  • Migration guide from core to pro
  • Comprehensive CHANGELOG entry (currently only 1 line changed)

High Priority Issues ⚠️

6. Test Utilities Fragile Dependency

File: packages/react-on-rails-pro/tests/registerServerComponent.client.test.jsx

Pro tests import from core test utils via relative path:

import { createNodeReadableStream } from '../../react-on-rails/tests/testUtils.js';

Recommendation: Create shared test utilities package or export from core.

7. Implementation Plan Checkboxes Don't Match Reality

Several checkboxes in docs/JS_PRO_PACKAGE_SEPARATION_PLAN.md are unchecked despite files existing.

Action Required: Update all checkboxes to reflect actual completion status.

8. Circular Dependency Risk

Pro package imports core and modifies globalThis.ReactOnRails, which core might access. This needs:

  • Runtime checks for version conflicts
  • Documentation of initialization order
  • Integration tests

Positive Highlights ⭐

Despite the issues, this PR has exceptional qualities:

  1. Outstanding Planning - 519-line implementation plan is exemplary
  2. Clean Git History - All files moved with git mv preserving history
  3. Consistent Architecture - Clear separation of concerns
  4. Good Error Messages - Helpful guidance for pro features
  5. Perfect License Compliance - Strict separation of MIT and Pro code
  6. Comprehensive Testing - Both packages well-tested
  7. Systematic Approach - Checkpoint-based implementation

Detailed Scores by Category

Category Score Notes
Architecture & Design 4.5/5 Excellent strategic decisions, minor DRY violations
Code Quality 4.0/5 Good implementation, some TS config complexity
Potential Issues 3.5/5 Circular dependency risk, build artifact issues
Test Coverage 4.0/5 Good coverage, needs integration tests
License Compliance 5.0/5 Perfect separation and compliance
Performance & Security 4.0/5 Good, needs bundle size verification
Documentation 4.5/5 Excellent plan, missing user docs
Code Standards 4.0/5 Generally compliant, verify linting

Recommendations for Next Steps

Before Merge (4-6 hours estimated)

  1. ✅ Fix code duplication in utils and loadJsonFile
  2. ✅ Add missing exports to core package.json
  3. ✅ Move react-on-rails-rsc to pro package only
  4. ✅ Add NOTICE file to pro package
  5. ✅ Create pro package README
  6. ✅ Update CHANGELOG with comprehensive entry
  7. ✅ Update implementation plan checkboxes
  8. ✅ Run bundle exec rubocop and rake autofix
  9. ✅ Fix test utilities imports
  10. ✅ Verify trailing newlines on all files

Follow-up PR

  • Add integration tests for core ↔ pro interaction
  • Create detailed migration guide
  • Add bundle size monitoring
  • Performance benchmarks

Final Verdict

Overall Score: 4.1 / 5.0 ⭐⭐⭐⭐

This is a well-executed major refactoring with excellent planning. The architectural decisions are sound, and the code quality is generally high. Once the critical issues are addressed, this will be a solid foundation for the Pro package separation.

Recommendation: APPROVE AFTER FIXES

The 10 critical items above must be resolved before merging, but the foundation is excellent and ready for production use once those issues are addressed.


🤖 Review generated by Claude Code

@claude
Copy link

claude bot commented Oct 2, 2025

Comprehensive Code Review

Overall Assessment: REQUEST CHANGES

This PR implements an excellent architectural improvement by separating JavaScript Pro functionality into a dedicated package. The planning is exemplary with a comprehensive 519-line implementation plan, proper license separation, and mostly sound technical execution. However, several critical issues must be addressed before merging.


Strengths

Exceptional Planning and Documentation

  • Comprehensive implementation plan in docs/JS_PRO_PACKAGE_SEPARATION_PLAN.md with 10 major steps, 30+ checkpoints, detailed success criteria, rollback procedures, and testing strategy
  • Clear architectural decisions documented upfront: dependency strategy, versioning, registry architecture, code reuse strategy
  • Methodical execution with progress tracking through checkboxes

Clean License Separation

  • Properly updated LICENSE.md removing obsolete pro paths and adding new pro package location
  • Clear licensing boundaries: Entire packages/react-on-rails/ now MIT, entire packages/react-on-rails-pro/ under Pro license
  • Correct package.json licensing: Pro package marked as license: UNLICENSED
  • Proper license headers in Pro files

Sound Package Architecture

  • Logical dependency model: Pro package depends on core (not peer dependency), following React/React-DOM pattern
  • Caret versioning strategy allowing minor/patch flexibility
  • Proper workspace configuration with enforced build order (core → pro)
  • DRY principle maintained with Pro package reusing core utilities

Critical Issues (Must Fix)

1. Missing TypeScript Declaration Files

File: packages/react-on-rails/package.json

Problem: Build script no longer includes --declaration flag

Impact:

  • No .d.ts files generated for core package
  • TypeScript users will have no type information
  • Pro package cannot import typed interfaces from core

Solution: Restore --declaration flag to core package build script.

2. Verify Core Package Exports Cleanup

File: packages/react-on-rails/package.json

Concern: Verify that core package.json removes ALL pro-related exports to avoid referencing non-existent files

Impact: Build failures, breaking changes, potential license violation

Solution: Verify final core package.json has no references to ./lib/pro/* paths.

3. Verify Pro Package Index Implementation

File: packages/react-on-rails-pro/src/index.ts

Required verification: Ensure this file properly imports core, imports pro registries, sets globalThis, and exports correctly

Impact: Without proper index.ts, pro package will not function correctly.

4. Significant Test Coverage Gaps

Pro Package Tests: Only 4 test files for 22+ pro source files

Critical missing tests:

  • No tests for packages/react-on-rails-pro/src/index.ts (main entry point)
  • No tests for CallbackRegistry async behavior
  • No tests for pro ComponentRegistry getOrWaitForComponent
  • No tests for pro StoreRegistry getOrWaitForStore
  • No integration tests combining core + pro

Recommendation: Add comprehensive test coverage before merge, especially for async registry methods and main entry point.


Major Concerns (Should Address)

5. Breaking Change in Core Package Behavior

File: packages/react-on-rails/src/clientStartup.ts

Change: Core package removes immediate hydration and page unload handling

Impact:

  • Different timing behavior could break existing applications
  • Memory leak potential - removing onPageUnloaded means components never unmount

Recommendation: Document breaking change in CHANGELOG, consider major version bump, provide migration guide

6. Dependency Version Misalignment Risk

Core: version: 16.1.1 | Pro: dependencies with react-on-rails: ^16.1.0

Risk: Versions can drift over time with no synchronization mechanism

Recommendation: Use exact version match, add pre-publish validation, consider lockstep publishing

7. globalThis Pollution Pattern

Concerns: No cleanup mechanism, potential conflicts if both packages loaded, server-side SSR considerations

Recommendation: Add defensive check for version conflicts


Minor Issues (Improvements)

  1. Add @deprecated JSDoc tags for stub methods that throw errors
  2. Include source maps in published packages for better debugging
  3. Add TypeScript project references for better IDE support

Recommended Test Plan

Build both packages, verify .d.ts generation, type check pro package, run all tests, verify no pro exports in core, and check license compliance.


Summary

Priority 0 (Blocking):

  1. Restore --declaration flag to core build
  2. Verify core package.json removes all pro exports
  3. Verify pro index.ts implementation is complete
  4. Add basic tests for pro index.ts integration

Priority 1 (Before Merge):

  1. Expand test coverage for pro package
  2. Document all breaking changes comprehensively
  3. Verify build system produces correct outputs

Priority 2 (Strongly Recommended):

  1. Add migration guide
  2. Implement version validation
  3. Add defensive globalThis handling

Closing Notes

This PR demonstrates excellent planning, clean license separation, and a sound architectural approach. The implementation plan is exemplary and could serve as a template for future major refactorings. The core concept is solid and the execution is largely correct.

The identified issues are primarily incomplete test coverage and missing build configuration—all solvable with focused effort. With the recommended changes, this PR will provide a clean, maintainable separation that benefits both project maintainability and user experience.

Estimated effort to address critical issues: 4-8 hours

Great work on the architecture and planning! Looking forward to seeing the updates.


Review generated with assistance from Claude Code

@claude
Copy link

claude bot commented Oct 2, 2025

Code Review: Split JS Pro Code to Separate Package

Summary

This PR implements a significant architectural refactoring to separate Pro JavaScript functionality into a standalone react-on-rails-pro package. The approach is well-documented with a comprehensive 519-line implementation plan. Overall, this is a well-architected change with clear separation of concerns.

✅ Strengths

Architecture & Design

  • Excellent documentation: The JS_PRO_PACKAGE_SEPARATION_PLAN.md is thorough with clear checkpoints, success criteria, and rollback procedures
  • Clean package separation: Following React's dependency model (Pro depends on Core with caret range ^16.1.0)
  • DRY principle maintained: Pro features layer over Core functionality, reducing duplication
  • Clear license boundaries: MIT and Pro code properly separated into distinct packages

Code Organization

  • Workspace setup: Proper monorepo configuration with both packages in workspaces
  • Sequential build ordering: package.json correctly builds core before pro (yarn workspace react-on-rails run build && yarn workspace react-on-rails-pro run build)
  • Export configuration: Pro package properly exports all necessary entry points (RSCRoute, RSCProvider, registerServerComponent, etc.)

Version Control

  • Git best practices: Files moved using git operations to preserve history
  • Commit structure: Clear merge commit structure

⚠️ Issues & Concerns

1. CRITICAL: Missing Core Package Implementation

The diff shows new core registries are referenced but NOT implemented in this PR:

  • packages/react-on-rails/src/ComponentRegistry.ts - ❌ Not in diff
  • packages/react-on-rails/src/StoreRegistry.ts - ❌ Not in diff
  • packages/react-on-rails/src/ClientRenderer.ts - ❌ Not in diff

Impact: This will break the build as ReactOnRails.client.ts and other files import from non-existent modules.

Files affected (from diff):

// packages/react-on-rails/src/ReactOnRails.client.ts references:
import * as ComponentRegistry from './ComponentRegistry'  // ❌ Doesn't exist
import * as StoreRegistry from './StoreRegistry'  // ❌ Doesn't exist

Recommendation: Verify these files exist in the actual branch. If missing, they must be added before merge.

2. Test Migration Incomplete

The implementation plan shows (in Step 5):

  • ✅ Checkpoint 5.1: Tests identified
  • ✅ Checkpoint 5.2: Tests moved
  • ❌ Checkpoint 5.3: "Verify all core tests pass" - INCOMPLETE

Concern: The plan shows test updates as pending but the PR is marked as ready for review.

Recommendation:

  • Run bundle exec rubocop (per CLAUDE.md requirements)
  • Run full test suite for both packages
  • Update test imports to reflect new package structure

3. Build Artifacts in .gitignore

New .gitignore rules added:

/packages/*/src/**/*.js
/packages/*/src/**/*.d.ts

Concern: These patterns might accidentally ignore legitimate test files in src/. The exclusions !/packages/*/src/**/*.test.js help but could miss other patterns.

Recommendation: Consider more specific patterns:

# Only ignore compiled TS output, not source files
/packages/*/src/**/*.js
!/packages/*/src/**/*.test.js
!/packages/*/src/**/*.spec.js
!/packages/*/src/**/*.config.js

4. Pro Package Index Implementation

From the plan, Step 6.1 shows the Pro package index.ts should:

  • Import all core functionality
  • Replace core with pro registries on globalThis.ReactOnRails

Concern: Cannot verify implementation without seeing the file. This is critical for the pro package to work correctly.

Recommendation: Ensure packages/react-on-rails-pro/src/index.ts properly:

  • Exports enhanced ReactOnRails with all core + pro features
  • Sets up pro registries correctly
  • Maintains backward compatibility

5. Import Path Changes Risk

The diff shows 56+ import statements changed from relative paths to package imports:

// Changed from:
import { ItemRegistrationCallback } from '../types/index.ts';
// To:
import { ItemRegistrationCallback } from 'react-on-rails/types';

Concerns:

  • TypeScript module resolution must be configured correctly in tsconfig.json
  • Risk of circular dependencies between packages
  • Build order dependencies must be respected

Recommendation:

  • Verify tsconfig.json has proper paths configuration
  • Test that yarn build works from clean state
  • Check for any circular dependency warnings

6. Potential Breaking Changes

While the architecture aims for backward compatibility, there are risks:

For Core users:

  • New registries throw errors for pro methods (getOrWaitForComponent etc.)
  • Error messages should be helpful, e.g., "getOrWaitForComponent requires react-on-rails-pro package. Install with: yarn add react-on-rails-pro"

For Pro users:

  • Must change import from 'react-on-rails' to 'react-on-rails-pro'
  • Should be documented in migration guide

Recommendation: Add/verify migration documentation exists for both user types.

7. License File Updates

LICENSE.md changes are good but note:

  • Old: packages/react-on-rails/src/pro/ removed ✅
  • New: packages/react-on-rails-pro/ added ✅
  • The NOTICE file was removed from packages/react-on-rails/src/pro/NOTICE

Recommendation: Verify Pro package has appropriate license headers in all source files.

📋 Testing Recommendations

Pre-Merge Testing

# From root directory
yarn install
yarn build  # Verify both packages build
yarn test   # Verify all tests pass
yarn type-check  # Verify TypeScript is happy
bundle exec rubocop  # MANDATORY per CLAUDE.md

# Test core package independently
cd packages/react-on-rails
yarn build && yarn test

# Test pro package independently  
cd packages/react-on-rails-pro
yarn build && yarn test

# Test in dummy app
cd spec/dummy
yarn install
yarn build
# Run Rails tests

Integration Testing

  1. Core-only scenario: Remove pro package, verify core functionality works
  2. Pro scenario: Install pro package, verify all features available
  3. Upgrade scenario: Test switching from core to pro import

🔒 Security & Performance

Security ✅

  • No security concerns identified
  • License separation prevents accidental Pro code in MIT package
  • No credential or secret handling changes

Performance ✅

  • Dependency strategy (Pro depends on Core) is efficient
  • No runtime performance impact expected
  • Build performance: Pro package adds build time but not significantly

📝 Documentation

Excellent:

  • ✅ Comprehensive implementation plan with 10 steps, 30+ checkpoints
  • ✅ Clear architectural decisions documented
  • ✅ Rollback strategy defined
  • ✅ Success criteria specified

Needs Attention:

  • ⚠️ User migration guide (Core → Pro upgrade path)
  • ⚠️ Error message documentation for users hitting pro stubs
  • ⚠️ README updates for both packages

🎯 Action Items Before Merge

Critical (Must Fix):

  1. ✅ Verify core registry files exist (ComponentRegistry.ts, StoreRegistry.ts, ClientRenderer.ts)
  2. ⚠️ Complete test migration (Step 5.3 in plan)
  3. ⚠️ Run bundle exec rubocop and fix all violations (per CLAUDE.md)
  4. ⚠️ Verify both packages build successfully from clean state
  5. ⚠️ Run full test suite and ensure all tests pass

High Priority (Should Fix):

  1. ⚠️ Verify Pro package index.ts implementation is complete
  2. ⚠️ Test workspace commands work (yarn build, yarn test from root)
  3. ⚠️ Verify no circular dependencies exist
  4. ⚠️ Add/update user migration documentation

Nice to Have:

  1. 📖 Add inline code comments explaining the dual-registry strategy
  2. 📖 Document TypeScript module resolution setup
  3. 🧪 Add integration tests for core→pro upgrade scenario

📊 Estimated Risk Level

Overall: MEDIUM-HIGH ⚠️

  • Architecture: ✅ LOW - Well designed and documented
  • Implementation: ⚠️ MEDIUM-HIGH - Key files potentially missing, incomplete testing
  • Testing: ⚠️ HIGH - Test migration incomplete per plan
  • Impact: ⚠️ HIGH - Major structural change affecting all users

💡 Recommendations

  1. Verify completeness: Ensure all files referenced exist in the branch
  2. Complete testing: Finish Step 5.3 and run full test suite
  3. Run linters: Execute bundle exec rubocop per repository requirements
  4. Test build: Verify clean build works: rm -rf node_modules packages/*/lib && yarn && yarn build
  5. Document migration: Add user-facing migration guide
  6. Consider staged rollout: Given the impact, consider feature flag or gradual rollout strategy

🎉 Conclusion

This is a well-architected and thoroughly planned refactoring. The approach follows best practices and the documentation is exemplary. However, the PR appears to be not yet fully implemented based on the diff analysis and incomplete checkpoints in the plan.

Recommendation: Address the critical items above, complete the test migration, and verify all builds pass before merging. Once those items are complete, this will be an excellent contribution to the codebase.

Great work on the planning and architecture! 🚀


Review generated by Claude Code following repository conventions from CLAUDE.md

@claude
Copy link

claude bot commented Oct 2, 2025

PR Review: Split JS Pro Code to Separate Package

This is an excellent and well-planned architectural refactoring! The separation of Pro functionality into a dedicated package is clean, well-documented, and thoughtfully executed. Here's my comprehensive review:


✅ Strengths

1. Outstanding Documentation & Planning

  • The JS_PRO_PACKAGE_SEPARATION_PLAN.md is exceptional - comprehensive, detailed, with clear checkpoints and validation steps
  • Architectural decisions are well-reasoned and documented (dependency strategy, versioning, registry architecture)
  • Clear migration path and rollback strategy provided

2. Clean Package Separation

  • Excellent dual-registry architecture: simple Map-based for core, CallbackRegistry-based for Pro
  • Clear feature split: synchronous (core) vs. async capabilities (pro)
  • Pro package properly re-exports all core functionality (export * from 'react-on-rails')
  • License boundaries are well-maintained

3. Code Quality

  • Good use of TypeScript with proper type imports
  • Error messages are helpful and guide users to upgrade (e.g., packages/react-on-rails/src/ComponentRegistry.ts:64)
  • Consistent code style and patterns across both packages
  • Pro package properly enhances rather than duplicates core functionality

4. License Compliance

  • LICENSE.md correctly updated to reflect new structure
  • Pro package has "license": "UNLICENSED" in package.json
  • Proper copyright headers in Pro files
  • Clear separation between MIT and Pro-licensed code

⚠️ Issues & Recommendations

CRITICAL: Dependency Version Mismatch

Issue: In packages/react-on-rails-pro/package.json:47

"dependencies": {
  "react-on-rails": "*"
}

Problem: The documentation states caret strategy (^16.1.0) but implementation uses * wildcard. This is inconsistent and potentially dangerous.

Recommendation: Change to explicit version range matching the plan:

"dependencies": {
  "react-on-rails": "^16.1.0"
}

Why: The * wildcard could allow major version mismatches and break compatibility. Caret range ensures major version alignment while allowing minor/patch updates.


Bug: Async/Await Return Type Mismatch

Issue: In packages/react-on-rails-pro/src/ClientSideRenderer.ts:31-58

The delegateToRenderer function is declared as async and returns Promise<boolean>, but the core package version (packages/react-on-rails/src/ClientRenderer.ts:31-60) is synchronous and returns boolean.

Problem:

  1. Line 53: await (component as RenderFunction)(props, railsContext, domNodeId); - but RenderFunction type might not return a Promise
  2. Inconsistency between core and pro implementations could cause issues if renderer functions don't return promises

Recommendation:

// Check if component returns a promise before awaiting
const result = (component as RenderFunction)(props, railsContext, domNodeId);
if (result && typeof result.then === 'function') {
  await result;
}
return true;

Or update type definitions to explicitly support both sync and async renderers.


Performance Consideration: Store Dependency Loading

Issue: In packages/react-on-rails-pro/src/ClientSideRenderer.ts:88-93

this.renderPromise = Promise.all(
  storeDependenciesArray.map((storeName) => StoreRegistry.getOrWaitForStore(storeName)),
).then(() => {
  if (this.state === 'unmounted') return Promise.resolve();
  return this.render(el, railsContext);
});

Concern: If any store dependency fails to load or takes too long, the component will never render. There's no timeout or error handling.

Recommendation: Add timeout and error handling:

const timeout = railsContext?.componentRegistryTimeout || 30000;
const storePromises = storeDependenciesArray.map((storeName) => 
  StoreRegistry.getOrWaitForStore(storeName)
    .catch(err => {
      console.error(`Failed to load store ${storeName}:`, err);
      throw err;
    })
);

this.renderPromise = Promise.race([
  Promise.all(storePromises),
  new Promise((_, reject) => 
    setTimeout(() => reject(new Error('Store dependencies timeout')), timeout)
  )
]).then(/* ... */);

Testing Gap: Missing Pro-Specific Async Tests

Observation: The implementation plan shows Step 5 checkpoints for moving tests are incomplete:

  • ✅ Checkpoint 5.1 & 5.2 completed (tests moved)
  • ❌ Checkpoint 5.2 incomplete: "Update test import paths to reflect new package structure"
  • ❌ Checkpoint 5.3 incomplete: "Verify all core tests pass"

Recommendation:

  1. Complete the test import path updates for pro tests
  2. Add specific tests for:
    • getOrWaitForComponent() async behavior
    • getOrWaitForStore() async behavior
    • Store dependency resolution in Pro ClientSideRenderer
    • Timeout scenarios for async operations
  3. Run full test suite to verify (mentioned in plan but not verified in PR)

Minor: Unused Parameter Warning

Issue: packages/react-on-rails/src/ComponentRegistry.ts:63

getOrWaitForComponent(_name: string): never {

Observation: Parameter is prefixed with _ but ESLint disable is used. This is fine, but consider if the error message should include the name:

throw new Error(`getOrWaitForComponent('${_name}') requires react-on-rails-pro package`);

This would match the pattern in StoreRegistry (lines 119-124).


Documentation: Missing README for Pro Package

Issue: Checkpoint 10.1 shows:

  • ✅ "Update core package README if needed"
  • ✅ "Create packages/react-on-rails-pro/README.md"

But I don't see the README in the file list.

Recommendation: Ensure the Pro package README is included with:

  • Installation instructions
  • Usage examples
  • Clear migration guide from core to pro
  • Feature comparison table

🔍 Security Considerations

No security concerns identified

  • License gating is appropriate for commercial software
  • No credential exposure or security vulnerabilities
  • Proper error handling for missing components/stores
  • No injection vulnerabilities in JSON parsing (uses built-in JSON.parse with proper error handling)

📊 Test Coverage Assessment

Core Package Tests (8 test files):

  • ✅ ComponentRegistry.test.js
  • ✅ StoreRegistry.test.js
  • ✅ ClientRenderer.test.ts (NEW - good!)
  • ✅ serverRenderReactComponent.test.ts
  • Plus 4 other existing tests

Pro Package Tests (5 test files):

  • ⚠️ Missing comprehensive async behavior tests
  • ✅ RSC-specific tests present
  • ⚠️ Test import paths may need updates (per implementation plan)

Recommendation: Achieve >80% coverage for new code, especially:

  • Pro ClientSideRenderer async paths
  • Registry async methods
  • Error scenarios and timeouts

🎯 Final Recommendations

Before Merging:

  1. MUST FIX: Change react-on-rails-pro/package.json dependency from "*" to "^16.1.0"
  2. SHOULD FIX: Add error handling and timeout for store dependency loading
  3. SHOULD FIX: Make delegateToRenderer handle both sync and async renderers properly
  4. SHOULD COMPLETE: Finish test migration (Step 5.2-5.3 in plan)
  5. SHOULD ADD: Pro package README with migration guide
  6. NICE TO HAVE: Add async behavior tests for Pro registry methods

Post-Merge:

  1. Monitor for any compatibility issues in production
  2. Document upgrade path in main project documentation
  3. Consider adding telemetry to track Pro vs Core usage patterns

📈 Overall Assessment

Rating: ⭐⭐⭐⭐ (4/5 stars)

This is excellent work with a very solid architectural foundation. The planning document alone is exemplary. The main issues are:

  • One critical dependency version fix needed
  • Some minor implementation gaps to complete
  • Testing coverage needs enhancement

Once the critical dependency version is fixed and tests are completed, this will be production-ready. Great job on maintaining clean separation between MIT and Pro code!


Additional Context: Reviewed against repository's CLAUDE.md guidelines - the code follows Prettier/RuboCop formatting standards, uses proper TypeScript conventions, and maintains clean package separation as documented.

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: 2

♻️ Duplicate comments (1)
packages/react-on-rails-pro/src/registerServerComponent/client.tsx (1)

18-18: Fix import order per ESLint rules.

The react-on-rails/types import should come before the relative import ../index.ts.

Apply this diff to fix the import order:

 import * as React from 'react';
-import ReactOnRails from '../index.ts';
+import ReactOnRails from '../index.ts';
+import { ReactComponentOrRenderFunction } from 'react-on-rails/types';
 import RSCRoute from '../RSCRoute.tsx';
-import { ReactComponentOrRenderFunction } from 'react-on-rails/types';
 import wrapServerComponentRenderer from '../wrapServerComponentRenderer/client.tsx';
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 85269e8 and bcfa227.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (8)
  • packages/react-on-rails-pro/jest.config.js (1 hunks)
  • packages/react-on-rails-pro/package.json (1 hunks)
  • packages/react-on-rails-pro/src/registerServerComponent/client.tsx (1 hunks)
  • packages/react-on-rails-pro/tests/SuspenseHydration.test.tsx (1 hunks)
  • packages/react-on-rails-pro/tests/injectRSCPayload.test.ts (1 hunks)
  • packages/react-on-rails-pro/tests/registerServerComponent.client.test.jsx (2 hunks)
  • packages/react-on-rails/jest.config.js (0 hunks)
  • packages/react-on-rails/tests/jest.setup.js (0 hunks)
💤 Files with no reviewable changes (2)
  • packages/react-on-rails/tests/jest.setup.js
  • packages/react-on-rails/jest.config.js
✅ Files skipped from review due to trivial changes (1)
  • packages/react-on-rails-pro/tests/SuspenseHydration.test.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/react-on-rails-pro/package.json
  • packages/react-on-rails-pro/tests/registerServerComponent.client.test.jsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Prettier is the sole authority for formatting all non-Ruby files; never manually format them

Files:

  • packages/react-on-rails-pro/jest.config.js
  • packages/react-on-rails-pro/src/registerServerComponent/client.tsx
  • packages/react-on-rails-pro/tests/injectRSCPayload.test.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for JS/TS code (lint via rake lint or yarn lint)

Files:

  • packages/react-on-rails-pro/jest.config.js
  • packages/react-on-rails-pro/src/registerServerComponent/client.tsx
  • packages/react-on-rails-pro/tests/injectRSCPayload.test.ts
🧠 Learnings (2)
📚 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:

  • packages/react-on-rails-pro/src/registerServerComponent/client.tsx
📚 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:

  • packages/react-on-rails-pro/src/registerServerComponent/client.tsx
🧬 Code graph analysis (1)
packages/react-on-rails-pro/jest.config.js (1)
react_on_rails_pro/spec/dummy/client/node-renderer.js (1)
  • process (5-5)
🪛 ESLint
packages/react-on-rails-pro/src/registerServerComponent/client.tsx

[error] 18-18: react-on-rails/types import should occur before import of ../index.ts

(import/order)

🪛 GitHub Actions: JS unit tests for Renderer package
packages/react-on-rails-pro/tests/injectRSCPayload.test.ts

[error] 2-2: Cannot find module 'react-on-rails/types' or its corresponding type declarations. (TS2307) — Command: yarn test

⏰ 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: claude-review
  • GitHub Check: rspec-package-tests (3.4, minimum)
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: rspec-package-tests (3.2, minimum)
  • GitHub Check: rspec-package-tests (3.2, latest)
  • GitHub Check: markdown-link-check
  • GitHub Check: build-and-test
🔇 Additional comments (9)
packages/react-on-rails-pro/jest.config.js (6)

1-2: LGTM! Appropriate use of eslint-disable for monorepo config sharing.

The relative import is correct for extending the workspace root Jest configuration, and the eslint-disable comment appropriately documents the intentional deviation from the import/no-relative-packages rule.


4-4: LGTM! Node version parsing is correct.

The logic correctly extracts the major version number from process.version for the conditional module mapping logic.


8-16: LGTM! Base config inheritance and package-specific overrides are correct.

The configuration properly extends the root config and sets appropriate package-specific test discovery and setup paths.


32-33: LGTM! Root directory correctly set to the package directory.

Setting rootDir: '.' ensures all Jest path references resolve relative to the packages/react-on-rails-pro/ directory.


18-27: No root moduleNameMapper to preserve—override is safe.

Likely an incorrect or invalid review comment.


29-30: No root transformIgnorePatterns found; current override stands
A repo-wide search shows transformIgnorePatterns is only defined in this file, so nothing needs merging.

packages/react-on-rails-pro/src/registerServerComponent/client.tsx (1)

51-59: Function implementation looks correct.

The registerServerComponent function correctly wraps each server component name with RSCRoute and wrapServerComponentRenderer, then registers them with ReactOnRails. The implementation aligns with the documented RSC pattern.

packages/react-on-rails-pro/tests/injectRSCPayload.test.ts (2)

6-142: LGTM! Comprehensive test coverage for RSC payload injection.

The test suite thoroughly covers the RSC payload injection scenarios including single/multiple payloads, chunk ordering, and delayed streaming. The test logic remains unchanged and continues to validate the correct behavior.


3-4: Files properly relocated. All imports now reference existing files in packages/react-on-rails-pro/src/ and the old pro/ directory has been removed.

@claude
Copy link

claude bot commented Oct 2, 2025

test

@claude
Copy link

claude bot commented Oct 2, 2025

Code Review: Split JS Pro Code to Separate Package

Summary

This PR implements a well-architected separation of Pro functionality into a standalone react-on-rails-pro package. The implementation is thorough, methodical, and includes excellent documentation. Overall, this is high-quality work with strong architectural decisions.

✅ Strengths

1. Excellent Documentation

  • Outstanding implementation plan with detailed checkpoints and validation steps in docs/JS_PRO_PACKAGE_SEPARATION_PLAN.md
  • Clear architectural decisions documented with rationale
  • Comprehensive rollback strategy and testing approach

2. Strong Architecture

  • Clean separation of concerns: Core (MIT) vs Pro (licensed) with clear boundaries
  • Smart dependency strategy: Pro uses core as dependency following React model rather than peer dependency
  • Logical feature split: Synchronous operations in core vs async operations in pro
  • DRY principle: Pro package reuses core functionality rather than duplicating

3. License Compliance

  • ✅ Proper license headers in Pro files
  • ✅ Clean separation with no Pro code in MIT directories
  • ✅ Updated LICENSE.md accurately reflects new structure
  • ✅ Pro package correctly marked as "license": "UNLICENSED"

4. Good Testing Approach

  • Unit tests updated for both packages
  • Core tests verify pro methods throw appropriate errors
  • Pro-specific tests moved to pro package
  • Test coverage maintained through the refactor

@claude
Copy link

claude bot commented Oct 2, 2025

Issues and Recommendations Part 1

1 CRITICAL Dependency Version Mismatch

Issue Documentation says react-on-rails caret 16.1.0 but packages react-on-rails-pro package.json line 47 shows dependencies react-on-rails wildcard

Impact Using wildcard is dangerous in production and does not match the documented versioning strategy

Recommendation Change to the documented caret range react-on-rails caret 16.1.0 in packages react-on-rails-pro package.json line 47

2 Build Script Issue Missing declaration Flag

Issue Core package build script removed declaration flag from yarn run clean and yarn run tsc

Impact TypeScript declaration files will not be generated breaking type imports for Pro package and users

Recommendation Restore the declaration flag in packages react-on-rails package.json build script

3 Incomplete Test Migration

Issue Per implementation plan Step 5 some checkpoints show incomplete for test import path updates and core tests verification

Recommendation Verify all test imports are updated to use package imports Run full test suite and ensure all tests pass before merging Update implementation plan checkmarks to reflect actual completion status

@claude
Copy link

claude bot commented Oct 2, 2025

Issues and Recommendations Part 2

4 Missing Export Configuration

Issue Core package removes RSC export react-server from exports

Recommendation Verify that applications currently importing RSC functionality are documented to migrate to the pro package Consider adding a migration guide section to the documentation

5 Sequential Build Dependency

Current root package.json has sequential build yarn workspace react-on-rails run build and yarn workspace react-on-rails-pro run build

Issue Sequential builds work but are not optimal for CI CD and do not explicitly declare dependency

Recommendation Document that core must build before pro or consider using workspace dependency management

6 Pro Package Index.ts Completeness

Issue Step 6.1 shows incomplete in the plan Create packages react-on-rails-pro src index.ts

Recommendation Verify the pro index.ts properly re-exports all core functionality enhances ReactOnRails with pro methods sets globalThis ReactOnRails correctly and exports all pro specific features

@claude
Copy link

claude bot commented Oct 2, 2025

Security Performance and Test Coverage

Security Considerations

No security concerns identified

  • No credential handling issues
  • No unsafe dynamic imports
  • License protection properly implemented
  • Clean separation prevents accidental exposure of Pro features

Performance Considerations

Generally good but worth noting

  • Pro package adds core as dependency consider bundle size
  • Dual registry system adds minimal overhead
  • CallbackRegistry uses Promise based waiting appropriate for use case

Recommendation Document expected bundle size impact for users upgrading from core to pro

Test Coverage Assessment

Strengths

  • Core registry tests verify basic functionality
  • Pro method stubs tested to throw appropriate errors
  • Pro specific tests moved to pro package

Gaps

  • Missing integration tests for core to pro upgrade path
  • No tests verifying workspace dependency resolution
  • Missing tests for error messages shown to users

Recommendation Add integration test that verifies core package throws errors for pro methods and pro package provides those methods

@claude
Copy link

claude bot commented Oct 2, 2025

Final Recommendations and Conclusion

Before Merging
1 Fix dependency version in pro package.json change wildcard to caret 16.1.0
2 Restore declaration flag in core package build script
3 Verify all tests pass both packages independently and workspace wide
4 Complete implementation plan checkmarks or update to reflect actual status
5 Run linting bundle exec rubocop and rake autofix per CLAUDE.md requirements
6 Add migration guide for users currently importing RSC from core

Nice to Have
1 Integration tests for upgrade path
2 Bundle size impact documentation
3 Performance benchmarks comparing before after
4 Example showing how to migrate from core to pro

Conclusion

This is excellent work with thoughtful architecture and thorough execution The separation is clean well documented and maintainable The issues identified are mostly minor and easily addressable

Recommendation Approve with changes Fix the critical dependency version issue and restore the declaration flag verify tests pass then merge

Great job on maintaining license boundaries and creating such comprehensive documentation

@claude
Copy link

claude bot commented Oct 2, 2025

Code Review: PR #1841 - Split JS Pro Code to Separate Package

Overview

This PR successfully implements a major architectural change - separating JavaScript Pro functionality into a standalone react-on-rails-pro package. The implementation is well-planned, thoroughly documented, and follows solid software engineering principles.


✅ Strengths

1. Excellent Planning & Documentation

  • The JS_PRO_PACKAGE_SEPARATION_PLAN.md is exceptional - detailed implementation plan with 10 major steps, 30+ checkpoints, and clear success criteria
  • Commit messages are descriptive and follow a clear checkpoint-based progression
  • All architectural decisions are documented with rationale (dependency strategy, versioning, registry architecture)

2. Clean Architecture

  • Clear separation of concerns: Core (MIT) vs Pro (commercial) functionality is well-defined
  • Dual registry system: Core has simple Map-based synchronous registries, Pro has advanced CallbackRegistry-based async registries
  • Smart dependency model: Pro package uses core as dependency (not peer), following React's pattern
  • Proper layering: Pro features are layered over core functionality, maximizing code reuse (DRY principle)

3. Git History Preservation

  • All file moves used git mv to preserve complete commit history (22 files moved from packages/react-on-rails/src/pro/)
  • This is crucial for maintaining code archaeology and understanding feature evolution

4. License Compliance

  • Clear separation between MIT and Pro licensed code
  • Updated LICENSE.md accurately reflects new package structure
  • Pro package correctly marked as "license": "UNLICENSED" in package.json
  • All Pro files have proper license headers

5. Progressive Implementation

  • All 10 major steps completed with checkpoints validated
  • Build system properly configured with sequential builds (core → pro)
  • Workspace configuration updated to include both packages

⚠️ Issues & Recommendations

1. Missing Documentation ⭐ HIGH PRIORITY

Issue: No README.md in packages/react-on-rails-pro/

Impact: Users/developers won't understand:

  • How to install and use the pro package
  • What features are available in pro vs core
  • Migration path from core to pro
  • API differences between packages

Recommendation:

# Create packages/react-on-rails-pro/README.md with:
# - Installation instructions
# - Feature comparison (core vs pro)
# - Usage examples
# - Migration guide
# - API reference for pro-specific methods

2. Test Coverage Gaps ⭐ MEDIUM PRIORITY

Issue: Only 5 test files in pro package, but 22 source files moved

Current tests:

  • injectRSCPayload.test.ts
  • registerServerComponent.client.test.jsx
  • SuspenseHydration.test.tsx
  • streamServerRenderedReactComponent.test.jsx
  • utils.test.js

Missing tests for:

  • CallbackRegistry.ts - Core pro functionality for async waiting
  • ComponentRegistry.ts (pro version) - Async component registration
  • StoreRegistry.ts (pro version) - Async store waiting
  • ClientSideRenderer.ts (pro) - Immediate hydration behavior
  • index.ts - Pro package entry point and integration

Recommendation: Add comprehensive unit tests for core pro registries and rendering logic

3. Potential Build Issues ⭐ MEDIUM PRIORITY

Issue: TypeScript incremental compilation configuration changed multiple times

Files affected:

  • Commit e47eee7b: Disabled incremental compilation
  • Commit 4d19c670: Re-enabled with tsBuildInfoFile in lib/
  • May cause inconsistent builds in CI/local environments

Recommendation:

// Verify tsconfig.json consistency across packages
// packages/react-on-rails-pro/tsconfig.json should match core pattern
{
  "compilerOptions": {
    "incremental": true,
    "tsBuildInfoFile": "./lib/.tsbuildinfo"
  }
}

4. Import Path Consistency ⭐ LOW PRIORITY

Finding: 46 imports from react-on-rails package across 16 files in pro package

Good: Import paths were updated correctly
Consider: Some imports could potentially use subpath exports for better tree-shaking:

// Current
import { getRailsContext } from 'react-on-rails/context';

// Could be more explicit in package.json exports
"exports": {
  "./context": "./lib/context.js",
  // ... other subpath exports
}

5. Error Messages for Core Users ✅ WELL DONE

The error stubs in core package are excellent:

getOrWaitForComponent(_name: string): never {
  throw new Error('getOrWaitForComponent requires react-on-rails-pro package');
}

Suggestion: Consider adding upgrade documentation link:

throw new Error(
  'getOrWaitForComponent requires react-on-rails-pro package. ' +
  'See: https://github.com/shakacode/react_on_rails/wiki/upgrading-to-pro'
);

🔍 Security Considerations

✅ Good Practices

  • No credentials or secrets in moved code
  • License boundaries properly enforced
  • No malicious code patterns detected

⚠️ Consider

  • Pro package has "license": "UNLICENSED" - ensure npm publish is configured to only publish to private registry or with proper license validation
  • Ruby-side license validation should prevent unauthorized use (appears to be in place based on CHANGELOG references)

🧪 Testing Recommendations

Before Merging

  1. Integration Tests: Test dummy Rails app with:

    • Core package only (verify pro methods throw helpful errors)
    • Pro package (verify all core + pro features work)
    • Switching from core to pro (verify seamless upgrade)
  2. Build Verification:

    yarn build  # Verify both packages build
    cd packages/react-on-rails && yarn test
    cd packages/react-on-rails-pro && yarn test
  3. License Validation: Manually verify no MIT code references pro files and vice versa


📊 Performance Considerations

✅ Good Decisions

  • Lazy loading ready: Pro async registry supports on-demand component loading
  • Code splitting friendly: Separate packages enable better bundle optimization
  • No duplication: Pro reuses core rendering logic

💡 Future Optimization

  • Consider tree-shaking improvements with more granular package.json exports
  • Monitor bundle size impact of dual package setup

🎯 Best Practices Adherence

✅ Follows CLAUDE.md Guidelines

  • CRITICAL: Need to verify RuboCop passes (Ruby code changes in LICENSE.md)
  • Proper git commit messages with co-authored-by
  • Clear architectural documentation
  • Incremental, well-tested changes

📝 Checklist Before Merge

  • Run bundle exec rubocop and fix violations
  • Add packages/react-on-rails-pro/README.md
  • Expand test coverage for pro registries
  • Verify all CI checks pass
  • Test in example Rails application
  • Update main project README to reference new package structure

🏆 Conclusion

Overall Assessment: ⭐⭐⭐⭐ (4/5)

This is a well-executed major refactoring with excellent planning and clean separation of concerns. The primary issues are:

  1. Missing pro package README (high priority)
  2. Incomplete test coverage (medium priority)
  3. Minor build configuration inconsistencies (low priority)

Recommendation: Request changes to add documentation and tests before merging. The architectural foundation is solid, but the package needs proper documentation for users and better test coverage for maintainability.

Why Not 5/5?

  • Missing critical user-facing documentation
  • Test coverage gaps for core pro functionality
  • Some build configuration churn suggests potential instability

Once documentation and tests are added, this will be an exemplary package separation! 🎉


Reviewed by Claude Code following repository CLAUDE.md guidelines

@claude
Copy link

claude bot commented Oct 2, 2025

PR Review: Split JS Pro Code to Separate Package

✅ Strengths

Excellent architectural planning and documentation:

  • The comprehensive implementation plan in docs/JS_PRO_PACKAGE_SEPARATION_PLAN.md is outstanding
  • Well-documented architectural decisions (dependency strategy, versioning, registry architecture)
  • Clear license compliance strategy with updated LICENSE.md

Sound architectural decisions:

  • Pro package as dependency (not peer dependency) follows React/React-DOM pattern
  • Caret range versioning aligns with industry best practices
  • Dual registry system with clear separation makes sense

Good test coverage:

  • Tests properly moved and organized for both packages
  • New tests added for core registries
  • Pro tests moved appropriately

⚠️ Issues and Concerns

1. Critical: Implementation Status Mismatch

The PR description states implementation is in progress but the plan document shows many checkpoints completed. This creates confusion about actual status.

Recommendation: Update either the PR description or the plan document to reflect actual status.

2. Missing Core Registry Files

The diff shows references to new core registries but I don't see the actual implementation files:

  • packages/react-on-rails/src/ComponentRegistry.ts
  • packages/react-on-rails/src/StoreRegistry.ts
  • packages/react-on-rails/src/ClientRenderer.ts

Recommendation: Ensure these core files are committed.

3. Pro Package Dependency Issue

In packages/react-on-rails-pro/package.json, using "*" as version specifier is problematic:

  • Goes against documented caret range strategy
  • Can cause unexpected behavior in production

Recommendation: Change to "react-on-rails": "^16.1.0"

4. Import Path Strategy Needs Clarity

Pro files import from react-on-rails/types, react-on-rails/pageLifecycle, etc. but core package.json may not export these paths.

Recommendation: Verify all subpath exports are properly configured in core package.json exports field.

5. Test Coverage Gaps

Missing:

  • Integration tests showing core-only vs pro usage
  • Tests verifying upgrade path
  • Tests confirming error messages for pro methods from core

Recommendation: Add tests that explicitly validate core isolation and pro functionality.

6. CHANGELOG.md Entry Incomplete

The CHANGELOG update references PR 1791 but should reference this PR 1841.

Recommendation: Update to reference PR 1841 and add as new entry.

🔒 Security Considerations

License Protection:

  • ✅ Good: Pro package correctly set as license UNLICENSED
  • ✅ Good: LICENSE.md properly updated
  • ⚠️ Concern: Ensure npm package prevents accidental public publishing

Recommendation: Add publishConfig with restricted access to pro package.json

🚀 Performance Considerations

Pro depending on core (vs peer dependency) has implications:

  • Bundle Size: Pro users include both packages
  • Build Time: Sequential builds increase CI time

Recommendation: Document bundle size impact and consider adding bundle size tests.

🎯 Required Actions Before Merge

  1. Verify all core registry files are committed
  2. Fix pro package dependency from "*" to "^16.1.0"
  3. Add/verify core package subpath exports
  4. Update CHANGELOG.md to reference PR 1841
  5. Run bundle exec rubocop and fix all violations
  6. Clarify implementation status
  7. Add integration tests for core-only and pro usage
  8. Review TypeScript build configuration

🏆 Overall Assessment

This is a well-planned architectural improvement with excellent documentation. The separation strategy is sound and follows industry best practices. However, there are several critical issues that must be addressed before merge, particularly around missing files, dependency versions, and implementation status clarity.

Recommendation: Address the critical issues above, then this will be ready for merge. The foundation is solid.


Review based on repository CLAUDE.md guidelines and React on Rails conventions

🤖 Generated with Claude Code

@claude
Copy link

claude bot commented Oct 2, 2025

🎯 Comprehensive PR Review: Split JS Pro Code to Separate Package

This is an excellent architectural refactoring that successfully separates Pro and Core functionality into distinct packages. The implementation is methodical, well-documented, and follows best practices.


Strengths

1. Outstanding Documentation & Planning

  • The JS_PRO_PACKAGE_SEPARATION_PLAN.md is exemplary with clear architectural decisions
  • Comprehensive commit messages with checkpoint tracking
  • All 10 major steps completed with detailed validation criteria

2. Clean Architectural Separation

  • Smart dependency strategy: Pro uses Core as dependency (not peer), following React model
  • Clear license boundaries: MIT in packages/react-on-rails/, Pro in packages/react-on-rails-pro/
  • Excellent DRY: Pro enhances Core rather than duplicating
  • Git history preserved via git mv for all moved files

3. Well-Designed Core Package

  • Simple synchronous registries using Map-based storage
  • Clear error messages for Pro-only features directing users to upgrade
  • Follows original pre-force-load behavior
  • No dependencies on Pro code

4. Robust Pro Package

  • Properly enhances Core with async capabilities
  • Exports everything from Core plus Pro features
  • Clean entry point that replaces globalThis.ReactOnRails
  • Supports immediate hydration and RSC

🔍 Issues & Recommendations

Critical Issues ⚠️

1. Missing Newline at End of Files
Per CLAUDE.md requirements, ALL files must end with newline character. This is CRITICAL and will cause CI failures.

2. RuboCop Must Be Run
Per CLAUDE.md: ALWAYS run bundle exec rubocop and fix ALL violations before every commit.

High Priority

3. Dependency Version Too Permissive
Documentation specifies caret range ^16.1.0, but final uses asterisk which is too permissive. Recommend workspace:* for development or ^16.1.0 for published packages.

4. TypeScript Type Safety in RSCRoute.tsx
Using (React as any).use(promise) bypasses type checking. Consider proper type guard or feature detection.

5. Potential Build Artifacts
Verify no build artifacts were accidentally committed in src/ directories despite new .gitignore patterns.

Medium Priority

6. Test Coverage Gaps

  • Verify all pro tests import from correct paths
  • Ensure core tests don't have residual pro dependencies
  • Run full test suite from root

7. Circular Dependency Risk
registerServerComponent/client.tsx imports from ../index.ts - verify no circular dependencies.

8. JSDoc Documentation
New registries could benefit from more comprehensive JSDoc with examples.


🔒 Security Review

No Security Concerns Found

  • No credential handling or sensitive data exposure
  • No eval() or unsafe dynamic code execution
  • License validation logic remains intact
  • No new external dependencies needing security review

🧪 Test Coverage Review

Good Coverage

  • Core registries have 12+ comprehensive unit tests
  • Pro functionality tests preserved with git history
  • Tests for error cases (Pro-only methods throwing errors)

⚠️ Verify Integration Tests
Ensure core works standalone, pro includes all core functionality, and upgrading is seamless.


🎯 Performance

No Regressions Expected

  • Core is simpler (no CallbackRegistry overhead)
  • Pro maintains existing async functionality
  • Build process properly optimized

📋 Before Merging Checklist

  • CRITICAL: Run bundle exec rubocop and fix all violations
  • CRITICAL: Ensure all files end with newline character
  • Fix dependency version in pro package.json
  • Verify no build artifacts committed
  • Run full test suite: yarn test
  • Check for circular dependencies
  • Test both packages independently

🎉 Final Verdict

This is excellent work! Architecturally sound, well-documented, maintains backward compatibility. Main blockers are mandatory RuboCop and trailing newline requirements per CLAUDE.md.

Implementation demonstrates:
✅ Strong architectural design
✅ Excellent documentation
✅ Clean git history
✅ Comprehensive testing
✅ Proper license separation
✅ DRY principles

Recommendation: Approve after addressing critical issues (RuboCop + trailing newlines)


Great work on this refactoring! The methodical approach with detailed planning and checkpoint tracking is exemplary. 🚀

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: 4

♻️ Duplicate comments (14)
packages/react-on-rails-pro/tests/jest.setup.js (2)

21-41: Fetch mock logic is sound; Node.js version requirement still applies.

The mock correctly converts Node Readable streams to web-standard ReadableStream using Readable.toWeb(). The path ../src/utils correctly resolves from this setup file's location.

However, the past review comment regarding Node.js version enforcement remains valid: Readable.toWeb() requires Node.js ≥16.5.0. Ensure the engines field in packages/react-on-rails-pro/package.json specifies this minimum version.


66-76: Console mock is incomplete; past recommendation still applies.

The past review correctly identified that this console mock only implements five methods (log, error, warn, info, debug), which can cause "undefined is not a function" errors if test code or dependencies call other console methods like console.table(), console.trace(), etc.

The suggested fix remains valid: preserve unmocked console methods by spreading the original console object before overriding specific methods with jest.fn().

packages/react-on-rails/src/ComponentRegistry.ts (1)

53-55: Avoid leaking the mutable registry Map.

Returning registeredComponents directly lets callers mutate the internal state (.clear(), .set()), which can dismantle the registry. Please return a defensive copy instead.

-  components(): Map<string, RegisteredComponent> {
-    return registeredComponents;
-  },
+  components(): Map<string, RegisteredComponent> {
+    return new Map(registeredComponents);
+  },
packages/react-on-rails-pro/src/index.ts (1)

123-137: Move lifecycle imports to the top-level import block.

ESLint still flags these two imports because they appear after the relative module imports—move them up with the other external imports to clear the lint failure.

-import * as ProStoreRegistry from './StoreRegistry.ts';
-import {
+import * as ProStoreRegistry from './StoreRegistry.ts';
+import {
   renderOrHydrateComponent,
   hydrateStore,
   renderOrHydrateAllComponents,
   hydrateAllStores,
   renderOrHydrateImmediateHydratedComponents,
   hydrateImmediateHydratedStores,
   unmountAll,
 } from './ClientSideRenderer.ts';
-import { onPageLoaded, onPageUnloaded } from 'react-on-rails/pageLifecycle';
-import { debugTurbolinks } from 'react-on-rails/turbolinksUtils';
+import { onPageLoaded, onPageUnloaded } from 'react-on-rails/pageLifecycle';
+import { debugTurbolinks } from 'react-on-rails/turbolinksUtils';
packages/react-on-rails-pro/tests/injectRSCPayload.test.ts (1)

2-2: Verify that the subpath export fix is complete.

The pipeline failure indicates that react-on-rails/types still cannot be resolved, despite being marked as addressed in previous commits (58d73f2 to a6ee325). This suggests either:

  1. The core package needs to be rebuilt
  2. The subpath export configuration is incomplete
  3. The types files are not being generated in the build output

Run the following script to verify the current state:

#!/bin/bash
# Verify the types subpath export is properly configured and built

echo "=== Checking core package.json exports for ./types ==="
if [ -f "packages/react-on-rails/package.json" ]; then
  jq '.exports."./types"' packages/react-on-rails/package.json || echo "ERROR: ./types export not found"
else
  echo "ERROR: packages/react-on-rails/package.json not found"
fi

echo -e "\n=== Checking if types directory exists in build output ==="
fd -t d 'types' packages/react-on-rails/lib packages/react-on-rails/dist 2>/dev/null || echo "No types directory found in build output"

echo -e "\n=== Checking if types files exist ==="
fd -t f -e ts -e js -e d.ts 'types' packages/react-on-rails/lib packages/react-on-rails/dist 2>/dev/null | head -10 || echo "No types files found"
packages/react-on-rails-pro/src/RSCRequestTracker.ts (1)

16-20: Verify types subpath export configuration.

This file uses the same react-on-rails/types subpath import that was flagged in the previous review and marked as addressed. Ensure the core package properly exports this subpath with built type definitions.

This is the same subpath export issue as in packages/react-on-rails-pro/tests/injectRSCPayload.test.ts. Verify that:

  1. The core package.json includes "./types" in its exports field
  2. The types are built and available at the specified path
  3. Both runtime files and TypeScript declarations are included
packages/react-on-rails/src/serverRenderReactComponent.ts (1)

149-167: Restore a local component lookup fallback.

Calling globalThis.ReactOnRails.getComponent(componentName) assumes the global is always initialized. In server bundles (and in the failing pipeline run) globalThis.ReactOnRails is often undefined, so this throws before rendering. Keep the existing ComponentRegistry lookup as a fallback (or gate the global access) so SSR remains stable.

-import createReactOutput from './createReactOutput.ts';
+import ComponentRegistry from './ComponentRegistry.ts';
+import createReactOutput from './createReactOutput.ts';
@@
-    const componentObj = globalThis.ReactOnRails.getComponent(componentName);
+    const componentObj =
+      globalThis.ReactOnRails?.getComponent?.(componentName) ??
+      ComponentRegistry.get(componentName);
docs/JS_PRO_PACKAGE_SEPARATION_PLAN.md (3)

215-230: Test migration remains incomplete.

As previously noted, Step 5 checkpoints for test import paths, Jest configuration, and test utilities are unchecked. Complete these to ensure test coverage is maintained.


236-244: Update checklist to reflect actual Pro entry point status.

As previously noted, the checklist claims the Pro entry point is incomplete, but packages/react-on-rails-pro/src/index.ts exists. Update the checklist to reflect the current implementation state.


502-518: Complete validation checklist before merge.

As previously noted, the manual and automated testing checklists are mostly unchecked. Ensure CI passes and all validation items are checked off before merging.

packages/react-on-rails/tests/serverRenderReactComponent.test.ts (1)

29-30: Fix unbound method causing context loss.

As previously noted, assigning ComponentRegistry.get directly loses class context. Inside get, any use of this (e.g., this.components()) will fail because this points to globalThis.ReactOnRails instead of ComponentRegistry.

Apply this fix:

-    globalThis.ReactOnRails = { getComponent: ComponentRegistry.get } as any;
+    globalThis.ReactOnRails = { getComponent: ComponentRegistry.get.bind(ComponentRegistry) } as any;
packages/react-on-rails/src/StoreRegistry.ts (3)

53-53: Remove debug console.log from production code.

As previously noted, line 53 contains a leftover debug statement that will pollute production console output.

Apply this diff:

    if (throwIfMissing) {
-      console.log('storeKeys', storeKeys);
      throw new Error(

101-103: Return defensive copy to prevent registry mutation.

As previously noted, returning the internal Map directly allows external code to mutate the registry.

Apply this diff:

  storeGenerators(): Map<string, StoreGenerator> {
-    return registeredStoreGenerators;
+    return new Map(registeredStoreGenerators);
  },

109-111: Return defensive copy to prevent registry mutation.

As previously noted, returning the internal Map directly allows external code to mutate the registry.

Apply this diff:

  stores(): Map<string, Store> {
-    return hydratedStores;
+    return new Map(hydratedStores);
  },
🧹 Nitpick comments (2)
packages/react-on-rails-pro/src/CallbackRegistry.ts (1)

127-128: Optional: Remove unnecessary initializations.

The promiseResolve and promiseReject variables are initialized with empty arrow functions but immediately replaced within the Promise constructor. You can simplify this by using definite assignment assertions.

Apply this diff to simplify the code:

-      let promiseResolve: (value: T | PromiseLike<T>) => void = () => {};
-      let promiseReject: (reason?: unknown) => void = () => {};
+      let promiseResolve!: (value: T | PromiseLike<T>) => void;
+      let promiseReject!: (reason?: unknown) => void;
       const promise = new Promise<T>((resolve, reject) => {
         promiseResolve = resolve;
         promiseReject = reject;
       });
react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ReduxSharedStoreApp.server.jsx (1)

3-3: Fix typo in comment.

The comment has "serer side" which should be "server side".

-// Top level component for serer side.
+// Top level component for server side.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bcfa227 and 8a1332e.

⛔ Files ignored due to path filters (3)
  • packages/react-on-rails-pro/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
  • react_on_rails_pro/spec/dummy/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (60)
  • .gitignore (1 hunks)
  • CHANGELOG.md (1 hunks)
  • LICENSE.md (1 hunks)
  • docs/JS_PRO_PACKAGE_SEPARATION_PLAN.md (1 hunks)
  • package.json (2 hunks)
  • packages/react-on-rails-pro/jest.config.js (1 hunks)
  • packages/react-on-rails-pro/package.json (1 hunks)
  • packages/react-on-rails-pro/src/CallbackRegistry.ts (1 hunks)
  • packages/react-on-rails-pro/src/ClientSideRenderer.ts (1 hunks)
  • packages/react-on-rails-pro/src/ComponentRegistry.ts (1 hunks)
  • packages/react-on-rails-pro/src/RSCProvider.tsx (1 hunks)
  • packages/react-on-rails-pro/src/RSCRequestTracker.ts (1 hunks)
  • packages/react-on-rails-pro/src/RSCRoute.tsx (1 hunks)
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts (2 hunks)
  • packages/react-on-rails-pro/src/StoreRegistry.ts (1 hunks)
  • packages/react-on-rails-pro/src/getReactServerComponent.client.ts (1 hunks)
  • packages/react-on-rails-pro/src/getReactServerComponent.server.ts (1 hunks)
  • packages/react-on-rails-pro/src/index.ts (1 hunks)
  • packages/react-on-rails-pro/src/injectRSCPayload.ts (1 hunks)
  • packages/react-on-rails-pro/src/loadJsonFile.ts (1 hunks)
  • packages/react-on-rails-pro/src/registerServerComponent/client.tsx (1 hunks)
  • packages/react-on-rails-pro/src/registerServerComponent/server.rsc.ts (1 hunks)
  • packages/react-on-rails-pro/src/registerServerComponent/server.tsx (1 hunks)
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts (2 hunks)
  • packages/react-on-rails-pro/src/transformRSCStreamAndReplayConsoleLogs.ts (1 hunks)
  • packages/react-on-rails-pro/src/wrapServerComponentRenderer/client.tsx (1 hunks)
  • packages/react-on-rails-pro/src/wrapServerComponentRenderer/server.tsx (1 hunks)
  • packages/react-on-rails-pro/tests/SuspenseHydration.test.tsx (1 hunks)
  • packages/react-on-rails-pro/tests/injectRSCPayload.test.ts (1 hunks)
  • packages/react-on-rails-pro/tests/jest.setup.js (1 hunks)
  • packages/react-on-rails-pro/tests/registerServerComponent.client.test.jsx (2 hunks)
  • packages/react-on-rails-pro/tests/streamServerRenderedReactComponent.test.jsx (1 hunks)
  • packages/react-on-rails-pro/tsconfig.json (1 hunks)
  • packages/react-on-rails/jest.config.js (0 hunks)
  • packages/react-on-rails/package.json (3 hunks)
  • packages/react-on-rails/src/ClientRenderer.ts (1 hunks)
  • packages/react-on-rails/src/ComponentRegistry.ts (1 hunks)
  • packages/react-on-rails/src/ReactOnRails.client.ts (3 hunks)
  • packages/react-on-rails/src/ReactOnRails.node.ts (1 hunks)
  • packages/react-on-rails/src/StoreRegistry.ts (1 hunks)
  • packages/react-on-rails/src/clientStartup.ts (2 hunks)
  • packages/react-on-rails/src/pro/NOTICE (0 hunks)
  • packages/react-on-rails/src/serverRenderReactComponent.ts (2 hunks)
  • packages/react-on-rails/tests/ClientRenderer.test.ts (1 hunks)
  • packages/react-on-rails/tests/ComponentRegistry.test.js (4 hunks)
  • packages/react-on-rails/tests/StoreRegistry.test.js (5 hunks)
  • packages/react-on-rails/tests/jest.setup.js (0 hunks)
  • packages/react-on-rails/tests/serverRenderReactComponent.test.ts (2 hunks)
  • packages/react-on-rails/tsconfig.json (1 hunks)
  • react_on_rails_pro/spec/dummy/client/app/packs/client-bundle.js (1 hunks)
  • react_on_rails_pro/spec/dummy/client/app/packs/server-bundle.js (1 hunks)
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/HelloWorldRehydratable.jsx (1 hunks)
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/LazyApolloGraphQLApp.client.tsx (1 hunks)
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/LazyApolloGraphQLApp.server.tsx (1 hunks)
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ReduxSharedStoreApp.client.jsx (1 hunks)
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ReduxSharedStoreApp.server.jsx (1 hunks)
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ServerComponentRouter.server.tsx (1 hunks)
  • react_on_rails_pro/spec/dummy/package.json (2 hunks)
  • spec/dummy/config/initializers/react_on_rails.rb (1 hunks)
  • tsconfig.json (1 hunks)
💤 Files with no reviewable changes (3)
  • packages/react-on-rails/src/pro/NOTICE
  • packages/react-on-rails/tests/jest.setup.js
  • packages/react-on-rails/jest.config.js
✅ Files skipped from review due to trivial changes (1)
  • packages/react-on-rails-pro/src/StoreRegistry.ts
🚧 Files skipped from review as they are similar to previous changes (22)
  • packages/react-on-rails-pro/tsconfig.json
  • packages/react-on-rails-pro/tests/registerServerComponent.client.test.jsx
  • CHANGELOG.md
  • packages/react-on-rails-pro/src/RSCProvider.tsx
  • packages/react-on-rails-pro/jest.config.js
  • packages/react-on-rails-pro/src/wrapServerComponentRenderer/server.tsx
  • packages/react-on-rails-pro/src/streamServerRenderedReactComponent.ts
  • packages/react-on-rails-pro/src/registerServerComponent/client.tsx
  • packages/react-on-rails/src/ReactOnRails.node.ts
  • packages/react-on-rails-pro/src/RSCRoute.tsx
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts
  • packages/react-on-rails-pro/tests/SuspenseHydration.test.tsx
  • packages/react-on-rails/tsconfig.json
  • LICENSE.md
  • packages/react-on-rails/src/ClientRenderer.ts
  • packages/react-on-rails-pro/src/ClientSideRenderer.ts
  • packages/react-on-rails-pro/src/transformRSCStreamAndReplayConsoleLogs.ts
  • tsconfig.json
  • package.json
  • packages/react-on-rails-pro/src/ComponentRegistry.ts
  • packages/react-on-rails-pro/src/wrapServerComponentRenderer/client.tsx
  • packages/react-on-rails-pro/src/loadJsonFile.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Prettier is the sole authority for formatting all non-Ruby files; never manually format them

Files:

  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/HelloWorldRehydratable.jsx
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ReduxSharedStoreApp.client.jsx
  • packages/react-on-rails-pro/src/CallbackRegistry.ts
  • packages/react-on-rails-pro/src/RSCRequestTracker.ts
  • packages/react-on-rails/src/StoreRegistry.ts
  • packages/react-on-rails-pro/tests/streamServerRenderedReactComponent.test.jsx
  • packages/react-on-rails/src/ReactOnRails.client.ts
  • packages/react-on-rails-pro/src/registerServerComponent/server.rsc.ts
  • packages/react-on-rails-pro/tests/injectRSCPayload.test.ts
  • packages/react-on-rails/src/serverRenderReactComponent.ts
  • react_on_rails_pro/spec/dummy/client/app/packs/client-bundle.js
  • packages/react-on-rails-pro/src/index.ts
  • packages/react-on-rails-pro/src/injectRSCPayload.ts
  • packages/react-on-rails-pro/tests/jest.setup.js
  • packages/react-on-rails-pro/src/registerServerComponent/server.tsx
  • packages/react-on-rails/package.json
  • packages/react-on-rails/tests/serverRenderReactComponent.test.ts
  • packages/react-on-rails-pro/package.json
  • packages/react-on-rails-pro/src/getReactServerComponent.client.ts
  • packages/react-on-rails/src/ComponentRegistry.ts
  • react_on_rails_pro/spec/dummy/client/app/packs/server-bundle.js
  • react_on_rails_pro/spec/dummy/package.json
  • packages/react-on-rails/tests/ComponentRegistry.test.js
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/LazyApolloGraphQLApp.server.tsx
  • packages/react-on-rails/src/clientStartup.ts
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ServerComponentRouter.server.tsx
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ReduxSharedStoreApp.server.jsx
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/LazyApolloGraphQLApp.client.tsx
  • docs/JS_PRO_PACKAGE_SEPARATION_PLAN.md
  • packages/react-on-rails/tests/StoreRegistry.test.js
  • packages/react-on-rails-pro/src/getReactServerComponent.server.ts
  • packages/react-on-rails/tests/ClientRenderer.test.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for JS/TS code (lint via rake lint or yarn lint)

Files:

  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/HelloWorldRehydratable.jsx
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ReduxSharedStoreApp.client.jsx
  • packages/react-on-rails-pro/src/CallbackRegistry.ts
  • packages/react-on-rails-pro/src/RSCRequestTracker.ts
  • packages/react-on-rails/src/StoreRegistry.ts
  • packages/react-on-rails-pro/tests/streamServerRenderedReactComponent.test.jsx
  • packages/react-on-rails/src/ReactOnRails.client.ts
  • packages/react-on-rails-pro/src/registerServerComponent/server.rsc.ts
  • packages/react-on-rails-pro/tests/injectRSCPayload.test.ts
  • packages/react-on-rails/src/serverRenderReactComponent.ts
  • react_on_rails_pro/spec/dummy/client/app/packs/client-bundle.js
  • packages/react-on-rails-pro/src/index.ts
  • packages/react-on-rails-pro/src/injectRSCPayload.ts
  • packages/react-on-rails-pro/tests/jest.setup.js
  • packages/react-on-rails-pro/src/registerServerComponent/server.tsx
  • packages/react-on-rails/tests/serverRenderReactComponent.test.ts
  • packages/react-on-rails-pro/src/getReactServerComponent.client.ts
  • packages/react-on-rails/src/ComponentRegistry.ts
  • react_on_rails_pro/spec/dummy/client/app/packs/server-bundle.js
  • packages/react-on-rails/tests/ComponentRegistry.test.js
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/LazyApolloGraphQLApp.server.tsx
  • packages/react-on-rails/src/clientStartup.ts
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ServerComponentRouter.server.tsx
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ReduxSharedStoreApp.server.jsx
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/LazyApolloGraphQLApp.client.tsx
  • packages/react-on-rails/tests/StoreRegistry.test.js
  • packages/react-on-rails-pro/src/getReactServerComponent.server.ts
  • packages/react-on-rails/tests/ClientRenderer.test.ts
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}

📄 CodeRabbit inference engine (CLAUDE.md)

{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}: All Ruby code must pass RuboCop with zero offenses before commit/push
RuboCop is the sole authority for Ruby file formatting; never manually format Ruby files

Files:

  • spec/dummy/config/initializers/react_on_rails.rb
🧠 Learnings (10)
📚 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:

  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/HelloWorldRehydratable.jsx
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ReduxSharedStoreApp.client.jsx
  • packages/react-on-rails-pro/tests/streamServerRenderedReactComponent.test.jsx
  • packages/react-on-rails/src/ReactOnRails.client.ts
  • packages/react-on-rails-pro/src/registerServerComponent/server.rsc.ts
  • react_on_rails_pro/spec/dummy/client/app/packs/client-bundle.js
  • packages/react-on-rails-pro/src/registerServerComponent/server.tsx
  • packages/react-on-rails-pro/src/getReactServerComponent.client.ts
  • react_on_rails_pro/spec/dummy/client/app/packs/server-bundle.js
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/LazyApolloGraphQLApp.server.tsx
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ReduxSharedStoreApp.server.jsx
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/LazyApolloGraphQLApp.client.tsx
📚 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:

  • packages/react-on-rails-pro/src/RSCRequestTracker.ts
  • packages/react-on-rails-pro/src/getReactServerComponent.client.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:

  • packages/react-on-rails-pro/src/RSCRequestTracker.ts
  • packages/react-on-rails-pro/src/getReactServerComponent.client.ts
  • react_on_rails_pro/spec/dummy/client/app/packs/server-bundle.js
📚 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:

  • packages/react-on-rails-pro/tests/streamServerRenderedReactComponent.test.jsx
  • packages/react-on-rails-pro/src/registerServerComponent/server.rsc.ts
  • packages/react-on-rails/src/serverRenderReactComponent.ts
  • packages/react-on-rails-pro/src/registerServerComponent/server.tsx
  • packages/react-on-rails/tests/serverRenderReactComponent.test.ts
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ServerComponentRouter.server.tsx
📚 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:

  • packages/react-on-rails/src/ReactOnRails.client.ts
  • packages/react-on-rails-pro/src/registerServerComponent/server.rsc.ts
  • packages/react-on-rails-pro/src/index.ts
  • packages/react-on-rails-pro/src/registerServerComponent/server.tsx
  • react_on_rails_pro/spec/dummy/client/app/packs/server-bundle.js
  • packages/react-on-rails/src/clientStartup.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:

  • packages/react-on-rails/src/ReactOnRails.client.ts
  • packages/react-on-rails-pro/src/index.ts
  • packages/react-on-rails/src/clientStartup.ts
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/LazyApolloGraphQLApp.client.tsx
📚 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:

  • react_on_rails_pro/spec/dummy/client/app/packs/client-bundle.js
  • packages/react-on-rails/package.json
  • react_on_rails_pro/spec/dummy/client/app/packs/server-bundle.js
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ServerComponentRouter.server.tsx
📚 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:

  • react_on_rails_pro/spec/dummy/client/app/packs/client-bundle.js
📚 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:

  • packages/react-on-rails/package.json
  • react_on_rails_pro/spec/dummy/client/app/packs/server-bundle.js
📚 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:

  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/LazyApolloGraphQLApp.server.tsx
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/LazyApolloGraphQLApp.client.tsx
  • packages/react-on-rails-pro/src/getReactServerComponent.server.ts
🧬 Code graph analysis (6)
packages/react-on-rails/src/ReactOnRails.client.ts (1)
packages/react-on-rails/src/ClientRenderer.ts (1)
  • reactOnRailsComponentLoaded (161-164)
packages/react-on-rails-pro/src/index.ts (6)
packages/react-on-rails-pro/src/StoreRegistry.ts (2)
  • storeGenerators (97-97)
  • stores (103-103)
react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ReduxSharedStoreApp.client.jsx (1)
  • store (32-32)
react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ReduxSharedStoreApp.server.jsx (2)
  • store (17-26)
  • store (19-19)
packages/react-on-rails-pro/src/ClientSideRenderer.ts (7)
  • renderOrHydrateComponent (257-266)
  • hydrateStore (293-312)
  • hydrateAllStores (317-318)
  • renderOrHydrateAllComponents (282-283)
  • unmountAll (325-328)
  • renderOrHydrateImmediateHydratedComponents (276-280)
  • hydrateImmediateHydratedStores (314-315)
packages/react-on-rails/src/turbolinksUtils.ts (1)
  • debugTurbolinks (14-22)
packages/react-on-rails/src/pageLifecycle.ts (2)
  • onPageLoaded (77-83)
  • onPageUnloaded (85-91)
spec/dummy/config/initializers/react_on_rails.rb (1)
lib/react_on_rails/react_component/render_options.rb (1)
  • immediate_hydration (102-104)
packages/react-on-rails/src/ComponentRegistry.ts (1)
packages/react-on-rails/src/isRenderFunction.ts (1)
  • isRenderFunction (11-31)
packages/react-on-rails/src/clientStartup.ts (3)
packages/react-on-rails/src/ReactOnRails.client.ts (1)
  • reactOnRailsPageLoaded (91-93)
packages/react-on-rails/src/turbolinksUtils.ts (1)
  • debugTurbolinks (14-22)
packages/react-on-rails/src/ClientRenderer.ts (1)
  • renderAllComponents (142-154)
packages/react-on-rails/tests/ClientRenderer.test.ts (1)
packages/react-on-rails/src/ClientRenderer.ts (2)
  • renderComponent (113-127)
  • reactOnRailsComponentLoaded (161-164)
🪛 ast-grep (0.39.5)
packages/react-on-rails/tests/ClientRenderer.test.ts

[warning] 15-15: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: domNode.innerHTML = '

Rendered: test
'
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 26-26: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: document.body.innerHTML = ''
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 27-27: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: document.head.innerHTML = ''
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 15-15: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: domNode.innerHTML = '

Rendered: test
'
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)

🪛 ESLint
packages/react-on-rails/src/ReactOnRails.client.ts

[error] 99-99: 'storeName' is defined but never used.

(@typescript-eslint/no-unused-vars)

packages/react-on-rails-pro/src/index.ts

[error] 123-123: react-on-rails/pageLifecycle import should occur before import of ./ComponentRegistry.ts

(import/order)


[error] 124-124: react-on-rails/turbolinksUtils import should occur before import of ./ComponentRegistry.ts

(import/order)

🪛 GitHub Actions: JS unit tests for Renderer package
packages/react-on-rails-pro/tests/injectRSCPayload.test.ts

[error] 2-2: Cannot find module 'react-on-rails/types' or its corresponding type declarations.

packages/react-on-rails-pro/src/index.ts

[error] 16-16: Cannot find module 'react-on-rails' or its corresponding type declarations.


[error] 19-19: Cannot find module 'react-on-rails/ReactOnRails.client' or its corresponding type declarations.


[error] 120-120: Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature.


[error] 123-123: Cannot find module 'react-on-rails/pageLifecycle' or its corresponding type declarations.


[error] 144-144: Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature.


[error] 149-149: Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature.

🪛 GitHub Actions: Lint JS and Ruby
packages/react-on-rails-pro/src/registerServerComponent/server.rsc.ts

[error] 1-1: Unused files detected by knip.

packages/react-on-rails-pro/src/registerServerComponent/server.tsx

[error] 1-1: Unused files detected by knip.

packages/react-on-rails-pro/src/getReactServerComponent.server.ts

[error] 1-1: Unused files detected by knip.

⏰ 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). (9)
  • 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: rspec-package-tests (3.4, latest)
  • GitHub Check: rspec-package-tests (3.2, minimum)
  • GitHub Check: rspec-package-tests (3.4, minimum)
  • GitHub Check: markdown-link-check
  • GitHub Check: claude-review
  • GitHub Check: build-and-test
🔇 Additional comments (34)
react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/HelloWorldRehydratable.jsx (2)

5-5: LGTM! Import correctly updated for Pro package.

The import change from 'react-on-rails' to 'react-on-rails-pro' is appropriate for this test fixture in the Pro package, aligning with the PR's dual import strategy where Pro features are accessed through the 'react-on-rails-pro' package.


56-56: Consistent usage of ReactOnRails from Pro package.

The ReactOnRails.render() call correctly uses the imported Pro package. Since 'react-on-rails-pro' exports both core and Pro features (as per the PR architecture), this usage is appropriate for the rehydration functionality demonstrated in this test fixture.

packages/react-on-rails-pro/src/CallbackRegistry.ts (2)

29-146: LGTM! Well-designed async registry implementation.

The CallbackRegistry class properly handles:

  • Async item registration with promise-based retrieval
  • Timeout mechanism to prevent indefinite waiting
  • Lifecycle management via page load/unload hooks
  • Race condition prevention with promise deduplication
  • Unused item tracking for identifying dead code
  • Clear error messages listing available items

The logic is correct and the TypeScript types are appropriate.


15-17: Subpath imports are valid
Verified that ./types, ./pageLifecycle, and ./context are exported in packages/react-on-rails/package.json; no changes needed.

packages/react-on-rails-pro/tests/jest.setup.js (3)

1-11: LGTM! Sentinel checks for polyfill obsolescence.

The guard checks correctly throw errors if TextEncoder or MessageChannel are already defined, alerting developers to remove the polyfills when jsdom adds native support.


13-19: LGTM! Core module imports are correct.

The conditional imports of Node.js core modules (util, stream, stream/web) are appropriate for polyfilling browser APIs in Jest. The use of eslint-disable-next-line global-require is justified since these imports are conditionally executed.


43-63: LGTM! Global polyfill assignments are correct.

The polyfills for TextEncoder, TextDecoder, MessageChannel, and ReadableStream are properly assigned to the global scope. The MessageChannel mock implementation correctly simulates the port1/port2 message-passing interface using optional chaining for safety.

packages/react-on-rails/package.json (1)

8-8: Verify tsconfig emission settings
The check script failed; manually confirm in packages/react-on-rails/tsconfig.json that compilerOptions.noEmit is false and compilerOptions.emitDeclarationOnly is false, ensuring yarn run tsc still emits the compiled artifacts.

packages/react-on-rails-pro/src/injectRSCPayload.ts (1)

17-18: Import path update looks correct.

The new paths line up with the shared core utilities and types—good adjustment.

packages/react-on-rails-pro/src/getReactServerComponent.client.ts (1)

17-19: Import ordering fix LGTM.

Type import now precedes the relative module, satisfying the lint rule.

packages/react-on-rails-pro/src/getReactServerComponent.server.ts (1)

17-19: Server import adjustments look good.

Ordering matches ESLint expectations—thanks for tidying this up.

packages/react-on-rails/tests/ComponentRegistry.test.js (1)

134-138: Nice guardrail around the Pro-only API.

This test keeps the community build from silently accessing Pro hooks—great to have explicit coverage here.

react_on_rails_pro/spec/dummy/client/app/packs/client-bundle.js (1)

3-3: LGTM! Import path correctly updated for Pro package.

The change from react-on-rails to react-on-rails-pro is appropriate for this Pro test fixture, aligning with the PR's architecture where Pro users import from the pro package to access all features.

react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ReduxSharedStoreApp.server.jsx (1)

7-7: LGTM! Import path correctly updated for Pro package.

The change aligns with the server-side component's usage in the Pro context.

react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/LazyApolloGraphQLApp.client.tsx (1)

6-6: LGTM! Type import correctly updated for Pro package.

The RailsContext type import from react-on-rails-pro is appropriate for this Pro client component.

react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ReduxSharedStoreApp.client.jsx (1)

8-8: LGTM! Import path correctly updated for Pro package.

The change is consistent with the server-side counterpart and aligns with the Pro package architecture.

react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ServerComponentRouter.server.tsx (1)

5-5: LGTM! Type imports correctly updated for Pro package.

The migration of RailsContext and ReactComponentOrRenderFunction types to react-on-rails-pro aligns with the package separation architecture, while Line 6 correctly continues to use the core package's server component wrapper utility.

react_on_rails_pro/spec/dummy/client/app/packs/server-bundle.js (1)

4-4: LGTM! Import path correctly updated for Pro package.

The change is consistent with the client bundle and appropriate for the Pro server bundle context.

packages/react-on-rails-pro/src/RSCRequestTracker.ts (1)

21-21: LGTM! Local utils import is correctly updated.

The change to import from './utils.ts' properly localizes the utility function within the Pro package.

react_on_rails_pro/spec/dummy/package.json (2)

54-54: LGTM: Dependency correctly updated to Pro package.

The dependency change from react-on-rails to react-on-rails-pro aligns with the PR objective to separate Pro functionality into its own package.


98-99: LGTM: Local linking scripts updated for Pro package.

The preinstall and link-source scripts correctly reference the Pro package and yalc publishing workflow for local development.

packages/react-on-rails/src/ReactOnRails.client.ts (4)

3-5: LGTM: Imports correctly updated to Core modules.

The imports now reference Core package modules (ClientRenderer.ts, ComponentRegistry.ts, StoreRegistry.ts) instead of Pro-specific implementations, correctly implementing the package separation.


96-96: LGTM: Delegation to Core ClientRenderer.

The method now correctly delegates to the Core implementation in ClientRenderer.ts.


100-100: LGTM: Appropriate Pro-required error.

The error message clearly indicates that store loading requires the Pro package, guiding users toward the upgrade path.


197-199: Confirm and document intent of setTimeout(...) around client startup

Wrapping ClientStartup.clientStartup in setTimeout(..., 0) defers initialization to the next event-loop tick and changes component mount timing.

File: packages/react-on-rails/src/ReactOnRails.client.ts Lines: 197-199

setTimeout(() => {
  ClientStartup.clientStartup();
}, 0);

Confirm this timing change is intentional and either (a) add a short in-file comment explaining the rationale (e.g., resolves a race, ensures DOM readiness, avoids synchronous React flush) and reference the PR/issue, or (b) revert the wrapper if it was accidental.

docs/JS_PRO_PACKAGE_SEPARATION_PLAN.md (1)

1-58: LGTM: Comprehensive architectural decisions documented.

The document clearly outlines the package dependency strategy, versioning approach, registry architecture, code reuse principles, and feature split. This provides excellent guidance for the implementation and future maintenance.

packages/react-on-rails/tests/serverRenderReactComponent.test.ts (1)

3-3: LGTM: Import updated to default export.

The import now uses the default export from ComponentRegistry.ts, consistent with the new registry architecture.

packages/react-on-rails/tests/ClientRenderer.test.ts (1)

1-205: LGTM: Comprehensive test coverage for ClientRenderer.

The test suite validates:

  • Component rendering with proper Rails context setup
  • Graceful handling of missing Rails context and DOM elements
  • Renderer function detection
  • The reactOnRailsComponentLoaded alias

The static analysis warnings about innerHTML are false positives—these are test utilities using hardcoded, safe strings.

packages/react-on-rails/src/clientStartup.ts (2)

1-11: LGTM: Core startup simplified to post-load rendering.

The changes correctly implement the Core package behavior: wait for full page load, then render all components. The imports now reference Core modules, and the comments clearly document the difference from Pro's immediate hydration capability.


28-30: LGTM: Behavioral differences documented.

The comments clearly distinguish Core (wait for full page load) from Pro (immediate hydration) behavior, providing helpful context for maintainers.

packages/react-on-rails/tests/StoreRegistry.test.js (2)

3-3: LGTM: Import updated to default export.

The import now uses the default export from StoreRegistry.ts, consistent with the new registry architecture.


90-119: LGTM: New tests validate Pro-only methods and map accessors.

The new tests ensure that:

  • Pro-only methods (getOrWaitForStore, getOrWaitForStoreGenerator) throw appropriate errors in Core
  • Map accessors (storeGenerators(), stores()) return correct registry snapshots
packages/react-on-rails/src/StoreRegistry.ts (2)

6-27: LGTM: Registration method correctly implemented.

The register method validates inputs, warns on duplicates, and stores generators in the registry.


119-139: LGTM: Pro-only methods throw appropriate errors.

The stub implementations for getOrWaitForStore and getOrWaitForStoreGenerator correctly throw errors directing users to upgrade to Pro, with helpful links.

Comment on lines +25 to +32
# TypeScript build artifacts in src (shouldn't be there, but just in case)
/packages/*/src/**/*.js
/packages/*/src/**/*.d.ts
/packages/*/src/**/*.d.cts
/packages/*/src/**/*.cjs
/packages/*/src/**/*.map
!/packages/*/src/**/*.test.js
!/packages/*/src/**/*.spec.js
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

❓ Verification inconclusive

Do not blanket-ignore /packages/*/src/**/*.js.

This pattern makes it impossible to commit any legitimate JavaScript sources (e.g., stories, config files, runtime JS) that live alongside TS in packages/*/src. Today or later, dropping one of those files would silently remove real code from the package. Please scope the ignore to the actual build output directory (e.g., /packages/*/lib) or otherwise restrict it so real JS sources remain trackable. Also double-check the repo for existing .js files under packages/*/src that would be affected.

Run this to list any currently tracked .js files that would be ignored:


🏁 Script executed:

#!/bin/bash
git ls-tree -r HEAD --name-only | rg '^packages/[^/]+/src/.*\.js$'

Length of output: 0


Restrict .gitignore to build output, not /packages/*/src/**/*.js
Even though no .js files currently exist under packages/*/src, a blanket-ignore could hide future legitimate JS sources. Change the patterns to target your build directory (e.g., /packages/*/lib/**/*.js, .map, etc.) instead of the src folder.

🤖 Prompt for AI Agents
In .gitignore around lines 25-32, the patterns are overly broad and ignore
JavaScript and map files inside packages/*/src which may hide legitimate source
files; change the ignore rules to target the actual build/output directories
(for example /packages/*/lib/** or /packages/*/dist/**) and their generated
artifacts (*.js, *.d.ts, *.map, etc.) instead of src so source files remain
tracked—update the patterns to point to the build folder(s) used by the repo and
remove or restrict the /packages/*/src/**/*.js, .d.ts, .cjs, .map entries.

Comment on lines +48 to +57
"dependencies": {
"react-on-rails": "*"
},
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 | 🟠 Major

Pin core dependency instead of "*"

Using a wildcard for react-on-rails can pull future breaking majors and contradicts the plan to use caret ranges (e.g., ^16.1.0). Please pin this dependency to an appropriate caret version to keep Pro aligned with the compatible core release.

🤖 Prompt for AI Agents
In packages/react-on-rails-pro/package.json around lines 48 to 50, the
dependency for react-on-rails is currently a wildcard ("*"); replace it with a
caret-pinned version that matches the compatible core release (for example
"^16.1.0") to avoid pulling future breaking majors, then run your package
manager to update the lockfile (npm install or yarn install) so the lockfile
reflects the pinned version.

Comment on lines 120 to 151
globalThis.ReactOnRails = ReactOnRailsPro;

// Pro client startup with immediate hydration support
import { onPageLoaded, onPageUnloaded } from 'react-on-rails/pageLifecycle';
import { debugTurbolinks } from 'react-on-rails/turbolinksUtils';

export async function reactOnRailsPageLoaded() {
debugTurbolinks('reactOnRailsPageLoaded [PRO]');
// Pro: Render all components that don't have immediate_hydration
await Promise.all([hydrateAllStores(), renderOrHydrateAllComponents()]);
}

function reactOnRailsPageUnloaded(): void {
debugTurbolinks('reactOnRailsPageUnloaded [PRO]');
unmountAll();
}

export function clientStartup() {
// Check if server rendering
if (globalThis.document === undefined) {
return;
}

// eslint-disable-next-line no-underscore-dangle
if (globalThis.__REACT_ON_RAILS_EVENT_HANDLERS_RAN_ONCE__) {
return;
}

// eslint-disable-next-line no-underscore-dangle
globalThis.__REACT_ON_RAILS_EVENT_HANDLERS_RAN_ONCE__ = true;

// Pro: Hydrate immediate_hydration components before page load
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 | 🟠 Major

Declare the globals you assign onto globalThis.

TypeScript is rejecting globalThis.ReactOnRails and globalThis.__REACT_ON_RAILS_EVENT_HANDLERS_RAN_ONCE__ as implicit any. Add a declaration block so these assignments are typed.

 const ReactOnRailsPro = {
   ...ReactOnRailsCore,
   // ...
 };
 
 declare global {
+  // eslint-disable-next-line no-var
+  var ReactOnRails: typeof ReactOnRailsPro;
+  // eslint-disable-next-line no-var
+  var __REACT_ON_RAILS_EVENT_HANDLERS_RAN_ONCE__: boolean | undefined;
 }
 
 // Replace global ReactOnRails with Pro version
 globalThis.ReactOnRails = ReactOnRailsPro;

This clears the “Element implicitly has an 'any' type” errors reported by the pipeline.

🧰 Tools
🪛 ESLint

[error] 123-123: react-on-rails/pageLifecycle import should occur before import of ./ComponentRegistry.ts

(import/order)


[error] 124-124: react-on-rails/turbolinksUtils import should occur before import of ./ComponentRegistry.ts

(import/order)

🪛 GitHub Actions: JS unit tests for Renderer package

[error] 120-120: Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature.


[error] 123-123: Cannot find module 'react-on-rails/pageLifecycle' or its corresponding type declarations.


[error] 144-144: Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature.


[error] 149-149: Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature.

🤖 Prompt for AI Agents
In packages/react-on-rails-pro/src/index.ts around lines 120 to 151, TypeScript
errors occur because assignments to globalThis.ReactOnRails and
globalThis.__REACT_ON_RAILS_EVENT_HANDLERS_RAN_ONCE__ are implicitly any;
declare these globals to satisfy the compiler by adding a declare global block
that augments GlobalThis with a typed ReactOnRails property (matching the
exported ReactOnRailsPro type) and a boolean
__REACT_ON_RAILS_EVENT_HANDLERS_RAN_ONCE__ (optional or boolean), placed either
at the top of this file or in a new .d.ts types file included by tsconfig; then
remove the implicit any errors without changing runtime logic.

@claude
Copy link

claude bot commented Oct 2, 2025

Pull Request Review: Split JS Pro Code to Separate Package

I've completed a comprehensive review of this PR. Overall, this is an excellent architectural refactoring that cleanly separates Pro features into a dedicated package. The implementation follows best practices and the comprehensive planning document demonstrates thorough preparation.

Strengths

1. Exceptional Planning and Documentation

  • The JS_PRO_PACKAGE_SEPARATION_PLAN.md is outstanding - it documents architectural decisions, provides detailed checkpoints, and includes success criteria
  • Clear rationale for each decision (dependency strategy, versioning, registry architecture)
  • Comprehensive rollback strategy

2. Clean Architecture & Design

  • Smart dependency strategy: Pro package depends on core (not peer dependency), following React's proven model
  • Clear separation of concerns: Core has simple synchronous registries, Pro has advanced async capabilities
  • DRY principle applied: Pro enhances core rather than duplicating code
  • License boundaries maintained: Strict separation between MIT and Pro-licensed code

3. Code Quality

  • Core registries (ComponentRegistry.ts, StoreRegistry.ts) are well-implemented with clear, focused APIs, helpful error messages for Pro-only methods, and good TypeScript typing
  • Pro package (index.ts) elegantly wraps and enhances core functionality
  • Proper error handling throughout

4. Test Coverage

  • Both core and pro packages have dedicated test suites
  • Core tests include validation that Pro-only methods throw appropriate errors
  • Tests properly migrated to respective packages
  • Good use of test utilities and fixtures

🔍 Issues & Concerns

Critical Issues

  1. Wildcard Dependency Version (packages/react-on-rails-pro/package.json:49)
    • Using "*" in dependencies can cause unpredictable behavior
    • Recommendation: Use workspace protocol "workspace:*" or explicit version "^16.1.0" to match documented strategy

High Priority Issues

  1. Missing TypeScript Declaration Files

    • The pro package.json includes TypeScript declarations in files but missing explicit declaration: true in tsconfig.json
    • Recommendation: Verify tsconfig.json includes "declaration: true" to ensure .d.ts files are generated
  2. Global Mutation in Pro Package (packages/react-on-rails-pro/src/index.ts:120)

    • globalThis.ReactOnRails = ReactOnRailsPro mutates global state during module import
    • Concern: Can cause issues with tree-shaking, SSR with concurrent requests, and test isolation
    • Recommendation: Consider explicit initialization function or document this behavior clearly
  3. Incomplete Test Migration

    • Step 5 checkpoints show tests identified and moved but import paths and Jest config verification unchecked
    • Recommendation: Complete test migration validation before merging
  4. Build Order Dependency

    • Root package.json shows sequential build which is good
    • Recommendation: Consider using workspace dependency features for automatic ordering

Medium Priority Issues

  1. Incomplete Implementation Plan Tracking

    • Several checkpoints remain unchecked in Steps 5, 9, and 10
    • Recommendation: Either complete remaining steps or update plan to reflect actual status
  2. Error Message Inconsistency

    • ComponentRegistry.ts:64 has simple error message while StoreRegistry.ts:120-124 has detailed error with upgrade instructions
    • Recommendation: Standardize error messages - include upgrade link in ComponentRegistry too
  3. Missing Pro Package README

    • Checkpoint 10.1 shows creating README but not visible in PR diff
    • Recommendation: Add README with installation/usage instructions

🔒 Security & License Compliance

Excellent separation:

  • Pro code properly isolated in packages/react-on-rails-pro/
  • LICENSE.md correctly updated to reflect new structure
  • Pro package correctly marked as "license": "UNLICENSED"
  • No Pro code remains in MIT-licensed directories

🎯 Performance Considerations

  • ✅ Pro package importing from core is efficient (no duplication)
  • ✅ Async methods in Pro enable better code-splitting
  • ⚠️ Global mutation during import may impact tree-shaking (noted above)

📋 Recommendations Summary

Before Merging:

  1. Fix wildcard dependency version to use workspace protocol or explicit version
  2. Verify TypeScript declarations are being generated
  3. Complete test migration validation (Steps 5.2, 5.3)
  4. Run full test suite and verify all tests pass
  5. Add README for pro package

Post-Merge:

  1. Document migration path for existing users
  2. Add integration tests for core-only and pro scenarios
  3. Set up CI checks for license compliance
  4. Consider improving error message consistency

Conclusion

This is high-quality work with excellent planning and execution. The architectural decisions are sound and well-documented. The main concerns are around completing the test migration validation and fixing the dependency version. With these addressed, this PR will provide a solid foundation for the Pro/Core separation.

Recommendation: Address critical and high-priority issues, then this is ready to merge.

Great work on this complex refactoring! 🚀

@claude
Copy link

claude bot commented Oct 2, 2025

Comprehensive Code Review - PR #1841

This PR successfully implements the separation of JavaScript Pro functionality into a dedicated react-on-rails-pro package. Excellent architecture and planning.

Strengths

  1. Excellent Architecture: Comprehensive implementation plan with 10 major steps and 30+ checkpoints
  2. Clean Code Separation: Core has simple Map-based registries, Pro has advanced CallbackRegistry
  3. Proper License Compliance: Strict MIT vs Pro boundaries maintained
  4. Good Test Coverage: Tests properly moved with git history preserved

Critical Issues to Address

1. Package Dependency Version (High Priority)

packages/react-on-rails-pro/package.json:49 uses wildcard dependency:

  • Current: "react-on-rails": "*"
  • Should be: "react-on-rails": "^16.1.0" or "workspace:^16.1.0"

This contradicts the implementation plan (line 22) and can cause version alignment issues.

2. RuboCop Compliance (Critical)

Per CLAUDE.md requirements:

  • Run: bundle exec rubocop
  • Run: rake autofix
  • Ensure all files end with newline character

3. Test Import Updates (Medium Priority)

Per implementation plan lines 215-217, test imports should be updated to use correct package imports. Verify these files:

  • packages/react-on-rails-pro/tests/registerServerComponent.client.test.jsx
  • packages/react-on-rails-pro/tests/streamServerRenderedReactComponent.test.jsx
  • packages/react-on-rails-pro/tests/SuspenseHydration.test.tsx
  • packages/react-on-rails-pro/tests/injectRSCPayload.test.ts

Recommendations

  1. Error Messages: Add installation instructions to Pro-only error messages in ComponentRegistry.ts:64 and StoreRegistry
  2. CI Jobs: Add explicit workspace-specific test jobs to catch issues early
  3. Documentation: Verify packages/react-on-rails-pro/README.md exists with complete migration guide

Security & Performance

  • No security issues identified
  • Immediate hydration properly implemented
  • AsyncRegistry pattern allows non-blocking loading

Pre-Merge Checklist

  • bundle exec rubocop passes
  • All files end with newline
  • Package version fixed to ^16.1.0
  • Pro test imports verified
  • CI passes for both packages
  • Documentation complete

Overall Assessment

Rating: Excellent (8.5/10)

Well-executed refactoring with excellent planning. Address the 3 critical issues above, then 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: 5

♻️ Duplicate comments (3)
packages/react-on-rails/tests/serverRenderReactComponent.test.ts (1)

29-31: Bind ComponentRegistry method to preserve class context.

Assigning ComponentRegistry.get directly to globalThis.ReactOnRails.getComponent loses the class context. If ComponentRegistry.get uses this internally (e.g., this.components()), it will fail when called via globalThis.ReactOnRails.getComponent.

Apply this diff to fix the context binding:

-    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/unbound-method, @typescript-eslint/no-explicit-any
-    globalThis.ReactOnRails = { getComponent: ComponentRegistry.get } as any;
+    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
+    globalThis.ReactOnRails = { getComponent: ComponentRegistry.get.bind(ComponentRegistry) } as any;

Note: The existing @typescript-eslint/unbound-method suppression comment indicates this issue was recognized but not resolved.

packages/react-on-rails/src/ClientRenderer.ts (1)

71-113: Move prop parsing inside the try/catch.

JSON.parse runs before the try, so malformed props throw without the component-scoped message—and we’ve already requested this fix earlier.

packages/react-on-rails-pro/package.json (1)

56-57: Pin react-on-rails to a caret range.

Keeping this as "*" will grab future breaking majors and violates the versioning strategy we discussed earlier—needs a caret range tied to the compatible core release.

🧹 Nitpick comments (2)
eslint.config.ts (1)

155-159: Consider consolidating with the existing packages config block.

The file pattern packages/**/src/**/* on line 155 overlaps with the broader packages/**/* pattern on line 149, and both apply the identical import/extensions rule. This creates redundant rule applications for files under packages/**/src/.

If the intent is to apply this rule to all package files (not just src/), you can remove this block entirely. Otherwise, clarify the distinction with a comment or adjust the patterns to be mutually exclusive.

Apply this diff to remove the redundant block:

   },
-  {
-    files: ['packages/**/src/**/*'],
-    rules: {
-      'import/extensions': ['error', 'ignorePackages'],
-    },
-  },
   {
     files: ['packages/react-on-rails-pro/**/*'],
packages/react-on-rails-pro/src/RSCRoute.tsx (1)

77-77: Update comment to reflect React 19 availability.

The comment states React.use is available in React 18.3+, but React 19 is now stable (released December 2024). Consider updating this comment to reflect current React version status, especially since the codebase may be targeting React 19.

Apply this diff to update the comment:

-  // React.use is available in React 18.3+
+  // React.use is available in React 18.3+ and is stable in React 19+

Based on learnings about React 19 stable release.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 019d131 and 6ec2eb6.

📒 Files selected for processing (20)
  • eslint.config.ts (3 hunks)
  • knip.ts (1 hunks)
  • packages/react-on-rails-pro/package.json (1 hunks)
  • packages/react-on-rails-pro/src/RSCRoute.tsx (2 hunks)
  • packages/react-on-rails-pro/src/ReactOnRails.client.ts (1 hunks)
  • packages/react-on-rails-pro/src/ReactOnRails.full.ts (1 hunks)
  • packages/react-on-rails-pro/src/ReactOnRails.node.ts (1 hunks)
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts (2 hunks)
  • packages/react-on-rails-pro/src/createReactOnRailsPro.ts (1 hunks)
  • packages/react-on-rails-pro/src/index.ts (1 hunks)
  • packages/react-on-rails/package.json (3 hunks)
  • packages/react-on-rails/src/ClientRenderer.ts (1 hunks)
  • packages/react-on-rails/src/ReactOnRails.client.ts (1 hunks)
  • packages/react-on-rails/src/ReactOnRails.full.ts (1 hunks)
  • packages/react-on-rails/src/base/client.ts (1 hunks)
  • packages/react-on-rails/src/base/full.ts (1 hunks)
  • packages/react-on-rails/src/clientStartup.ts (2 hunks)
  • packages/react-on-rails/src/createReactOnRails.ts (1 hunks)
  • packages/react-on-rails/tests/serverRenderReactComponent.test.ts (2 hunks)
  • spec/dummy/spec/helpers/react_on_rails_helper_spec.rb (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/react-on-rails-pro/src/ReactOnRailsRSC.ts
  • spec/dummy/spec/helpers/react_on_rails_helper_spec.rb
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Prettier is the sole authority for formatting all non-Ruby files; never manually format them

Files:

  • packages/react-on-rails-pro/src/index.ts
  • packages/react-on-rails-pro/src/ReactOnRails.client.ts
  • packages/react-on-rails/src/base/full.ts
  • packages/react-on-rails-pro/src/ReactOnRails.full.ts
  • packages/react-on-rails/src/base/client.ts
  • packages/react-on-rails/src/ReactOnRails.full.ts
  • eslint.config.ts
  • packages/react-on-rails/src/ReactOnRails.client.ts
  • packages/react-on-rails-pro/src/createReactOnRailsPro.ts
  • packages/react-on-rails-pro/src/RSCRoute.tsx
  • packages/react-on-rails/src/createReactOnRails.ts
  • packages/react-on-rails/src/clientStartup.ts
  • packages/react-on-rails/src/ClientRenderer.ts
  • knip.ts
  • packages/react-on-rails-pro/package.json
  • packages/react-on-rails-pro/src/ReactOnRails.node.ts
  • packages/react-on-rails/package.json
  • packages/react-on-rails/tests/serverRenderReactComponent.test.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for JS/TS code (lint via rake lint or yarn lint)

Files:

  • packages/react-on-rails-pro/src/index.ts
  • packages/react-on-rails-pro/src/ReactOnRails.client.ts
  • packages/react-on-rails/src/base/full.ts
  • packages/react-on-rails-pro/src/ReactOnRails.full.ts
  • packages/react-on-rails/src/base/client.ts
  • packages/react-on-rails/src/ReactOnRails.full.ts
  • eslint.config.ts
  • packages/react-on-rails/src/ReactOnRails.client.ts
  • packages/react-on-rails-pro/src/createReactOnRailsPro.ts
  • packages/react-on-rails-pro/src/RSCRoute.tsx
  • packages/react-on-rails/src/createReactOnRails.ts
  • packages/react-on-rails/src/clientStartup.ts
  • packages/react-on-rails/src/ClientRenderer.ts
  • knip.ts
  • packages/react-on-rails-pro/src/ReactOnRails.node.ts
  • packages/react-on-rails/tests/serverRenderReactComponent.test.ts
🧠 Learnings (10)
📓 Common learnings
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-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:

  • packages/react-on-rails-pro/src/index.ts
  • packages/react-on-rails-pro/src/ReactOnRails.full.ts
  • packages/react-on-rails/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:

  • packages/react-on-rails-pro/src/index.ts
  • packages/react-on-rails-pro/src/ReactOnRails.client.ts
  • packages/react-on-rails-pro/src/ReactOnRails.full.ts
  • packages/react-on-rails/src/ReactOnRails.client.ts
  • packages/react-on-rails-pro/src/createReactOnRailsPro.ts
  • packages/react-on-rails/src/clientStartup.ts
  • packages/react-on-rails/src/ClientRenderer.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:

  • packages/react-on-rails-pro/src/ReactOnRails.full.ts
  • packages/react-on-rails-pro/package.json
📚 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:

  • packages/react-on-rails-pro/src/createReactOnRailsPro.ts
  • packages/react-on-rails/src/clientStartup.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:

  • packages/react-on-rails-pro/src/RSCRoute.tsx
📚 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:

  • packages/react-on-rails-pro/src/RSCRoute.tsx
  • packages/react-on-rails/tests/serverRenderReactComponent.test.ts
📚 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:

  • packages/react-on-rails-pro/src/RSCRoute.tsx
📚 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:

  • packages/react-on-rails/src/ClientRenderer.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:

  • packages/react-on-rails/package.json
🧬 Code graph analysis (11)
packages/react-on-rails-pro/src/ReactOnRails.client.ts (2)
packages/react-on-rails-pro/src/createReactOnRailsPro.ts (1)
  • createReactOnRailsPro (66-132)
packages/react-on-rails/src/base/client.ts (1)
  • createBaseClientObject (38-199)
packages/react-on-rails/src/base/full.ts (2)
packages/react-on-rails/src/base/client.ts (3)
  • createBaseClientObject (38-199)
  • handleError (187-191)
  • serverRenderReactComponent (181-185)
packages/react-on-rails/src/types/index.ts (2)
  • ErrorOptions (236-243)
  • RenderParams (220-224)
packages/react-on-rails-pro/src/ReactOnRails.full.ts (2)
packages/react-on-rails-pro/src/createReactOnRailsPro.ts (1)
  • createReactOnRailsPro (66-132)
packages/react-on-rails/src/base/full.ts (1)
  • createBaseFullObject (15-30)
packages/react-on-rails/src/base/client.ts (2)
packages/react-on-rails/src/types/index.ts (6)
  • ReactComponentOrRenderFunction (178-178)
  • StoreGenerator (184-184)
  • Store (183-183)
  • ReactOnRailsOptions (270-275)
  • AuthenticityHeaders (180-180)
  • RenderReturnType (268-268)
packages/react-on-rails/src/createReactOutput.ts (1)
  • createReactOutput (36-92)
packages/react-on-rails/src/ReactOnRails.full.ts (2)
packages/react-on-rails/src/createReactOnRails.ts (1)
  • createReactOnRails (12-88)
packages/react-on-rails/src/base/full.ts (1)
  • createBaseFullObject (15-30)
packages/react-on-rails/src/ReactOnRails.client.ts (3)
packages/react-on-rails/src/types/index.ts (1)
  • ReactOnRails (277-345)
packages/react-on-rails/src/createReactOnRails.ts (1)
  • createReactOnRails (12-88)
packages/react-on-rails/src/base/client.ts (1)
  • createBaseClientObject (38-199)
packages/react-on-rails-pro/src/createReactOnRailsPro.ts (6)
packages/react-on-rails/src/base/client.ts (2)
  • createBaseClientObject (38-199)
  • reactOnRailsPageLoaded (164-168)
packages/react-on-rails/src/base/full.ts (1)
  • createBaseFullObject (15-30)
packages/react-on-rails/src/turbolinksUtils.ts (1)
  • debugTurbolinks (14-22)
packages/react-on-rails-pro/src/ClientSideRenderer.ts (7)
  • hydrateAllStores (317-318)
  • renderOrHydrateAllComponents (282-283)
  • unmountAll (325-328)
  • renderOrHydrateImmediateHydratedComponents (276-280)
  • hydrateImmediateHydratedStores (314-315)
  • renderOrHydrateComponent (257-266)
  • hydrateStore (293-312)
packages/react-on-rails/src/clientStartup.ts (1)
  • clientStartup (13-31)
packages/react-on-rails/src/pageLifecycle.ts (2)
  • onPageLoaded (77-83)
  • onPageUnloaded (85-91)
packages/react-on-rails/src/createReactOnRails.ts (4)
packages/react-on-rails/src/base/client.ts (3)
  • createBaseClientObject (38-199)
  • reactOnRailsPageLoaded (164-168)
  • reactOnRailsComponentLoaded (171-175)
packages/react-on-rails/src/base/full.ts (1)
  • createBaseFullObject (15-30)
packages/react-on-rails-pro/src/createReactOnRailsPro.ts (2)
  • reactOnRailsPageLoaded (78-80)
  • reactOnRailsComponentLoaded (82-84)
packages/react-on-rails/src/clientStartup.ts (1)
  • clientStartup (13-31)
packages/react-on-rails/src/clientStartup.ts (2)
packages/react-on-rails/src/turbolinksUtils.ts (1)
  • debugTurbolinks (14-22)
packages/react-on-rails/src/ClientRenderer.ts (1)
  • renderAllComponents (140-152)
packages/react-on-rails/src/ClientRenderer.ts (6)
packages/react-on-rails/src/types/index.ts (1)
  • RenderReturnType (268-268)
packages/react-on-rails/src/createReactOutput.ts (1)
  • createReactOutput (36-92)
packages/react-on-rails/src/isServerRenderResult.ts (1)
  • isServerRenderHash (8-17)
packages/react-on-rails/src/context.ts (1)
  • getRailsContext (14-32)
packages/react-on-rails/src/reactApis.cts (2)
  • supportsRootApi (10-10)
  • unmountComponentAtNode (50-53)
packages/react-on-rails/src/pageLifecycle.ts (1)
  • onPageUnloaded (85-91)
packages/react-on-rails-pro/src/ReactOnRails.node.ts (2)
packages/react-on-rails-pro/src/createReactOnRailsPro.ts (1)
  • streamServerRenderedReactComponent (109-113)
packages/react-on-rails/src/createReactOnRails.ts (1)
  • streamServerRenderedReactComponent (65-67)
🪛 GitHub Actions: Lint JS and Ruby
packages/react-on-rails/src/base/full.ts

[warning] 32-32: Unused exported type: BaseFullObjectType - full.ts:32:13

packages/react-on-rails/src/base/client.ts

[warning] 201-201: Unused exported type: BaseClientObjectType - client.ts:201:13

knip.ts

[warning] 1-1: Unused item in ignoreBinaries: nps


[warning] 1-1: Unused item in ignoreDependencies (workspace: spec/dummy): @babel/runtime


[warning] 1-1: Configuration hints


[error] 1-1: Command failed with exit code 1.

⏰ 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: markdown-link-check
  • 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: rspec-package-tests (3.4, minimum)
  • GitHub Check: build-and-test
🔇 Additional comments (10)
eslint.config.ts (2)

160-167: ESLint check failed due to missing “jiti.”
Install jiti (npm install --save-dev jiti) or convert your ESLint config to JavaScript, then rerun the import/named check to confirm no legitimate import errors are being suppressed.


193-193: Run full type‐check locally to confirm no TS errors

The sandbox failed due to missing dev-dependencies (@types/react, @types/react-dom, jiti, etc.), not because of the projectService: true change. Please install all devDependencies and run:

npx tsc --noEmit
npx eslint 'packages/**/*.ts' 'packages/**/*.tsx' --max-warnings 0

to ensure no type or lint errors with the new config.

packages/react-on-rails-pro/src/ReactOnRails.node.ts (1)

1-23: LGTM! Pro Node.js surface correctly wired.

The file correctly:

  • Applies the Pro license header
  • Wires streamServerRenderedReactComponent to the Pro implementation
  • Re-exports the full ReactOnRails surface with proper default export handling
  • Maintains consistency with the dual-package architecture
packages/react-on-rails/package.json (2)

41-59: LGTM! Comprehensive exports map for granular imports.

The expanded exports map provides:

  • Granular access to specific modules (types, utils, context, etc.)
  • Internal API exposure via @internal/ prefix for Pro package integration
  • Proper dual entrypoints (client vs. full)

This design enables the Pro package to selectively import from core while maintaining clear API boundaries.


8-8: Confirm root-level build orchestration

packages/react-on-rails uses "yarn run clean && yarn run tsc". Root-level inspection failed (jq errored). Confirm the repo root package.json has a build script that runs both react-on-rails and react-on-rails-pro or that workspace-level coordination exists. From the repo root run:
jq -r '.scripts.build' package.json
jq -r '.workspaces' package.json

packages/react-on-rails-pro/src/index.ts (1)

1-18: LGTM! Pro package index correctly delegates to client entrypoint.

The simplified implementation:

  • Applies proper Pro license header
  • Re-exports all functionality from ReactOnRails.client.ts
  • Maintains a clean public API surface
  • Resolves all previous review concerns about import ordering and module resolution by delegating complexity to the client module

This is a much cleaner design than the previous iteration discussed in past review comments.

packages/react-on-rails/src/ReactOnRails.full.ts (1)

1-7: LGTM! Core full bundle correctly constructed.

The file properly:

  • Uses the factory pattern with createReactOnRails(createBaseFullObject)
  • Re-exports all types from the types module
  • Provides a clean default export
  • Maintains MIT licensing (no Pro features)

This establishes the core full-featured bundle for server-side rendering in the MIT package.

packages/react-on-rails-pro/src/ReactOnRails.client.ts (1)

1-21: LGTM! Pro client properly constructed from base client.

The file correctly:

  • Applies Pro license header
  • Imports base client via the @internal/ path exposed in core package exports
  • Uses createReactOnRailsPro factory to wrap base with Pro features
  • Re-exports types from core
  • Provides clean default export

This establishes the Pro client bundle with async component/store loading capabilities.

packages/react-on-rails-pro/src/ReactOnRails.full.ts (2)

18-25: LGTM! Helpful optimization warning for browser bundles.

The browser-specific warning appropriately alerts developers when server-rendering code (~14KB) is included in client bundles, with a link to optimization guidance. This is a useful developer experience enhancement.


1-30: LGTM! Pro full bundle correctly constructed.

The file properly:

  • Applies Pro license header
  • Imports base full object via @internal/ path
  • Uses createReactOnRailsPro factory to wrap base with Pro features
  • Warns developers about bundle size optimization opportunities
  • Re-exports types and provides clean default export

This establishes the Pro full-featured bundle for server-side rendering with Pro capabilities.

knip.ts Outdated
Comment on lines 42 to 84
entry: ['src/ReactOnRails.node.ts!'],
project: ['src/**/*.[jt]s{x,}!', 'tests/**/*.[jt]s{x,}', '!lib/**'],
ignore: [
// Jest setup and test utilities - not detected by Jest plugin in workspace setup
'tests/jest.setup.js',
// Build output directories that should be ignored
'lib/**',
],
},

// React on Rails Pro package workspace
'packages/react-on-rails-pro': {
entry: [
'src/ReactOnRails.node.ts!',
'src/pro/ReactOnRailsRSC.ts!',
'src/pro/registerServerComponent/client.tsx!',
'src/pro/registerServerComponent/server.tsx!',
'src/pro/registerServerComponent/server.rsc.ts!',
'src/pro/wrapServerComponentRenderer/server.tsx!',
'src/pro/wrapServerComponentRenderer/server.rsc.tsx!',
'src/pro/RSCRoute.tsx!',
'src/pro/ServerComponentFetchError.ts!',
'src/pro/getReactServerComponent.server.ts!',
'src/pro/transformRSCNodeStream.ts!',
'src/loadJsonFile.ts!',
'src/ReactOnRails.full.ts!',
'src/ReactOnRails.client.ts!',
'src/index.ts!',
'src/ReactOnRailsRSC.ts!',
'src/registerServerComponent/client.tsx!',
'src/registerServerComponent/server.tsx!',
'src/registerServerComponent/server.rsc.ts!',
'src/wrapServerComponentRenderer/server.tsx!',
'src/wrapServerComponentRenderer/server.rsc.tsx!',
'src/RSCRoute.tsx!',
'src/ServerComponentFetchError.ts!',
'src/getReactServerComponent.server.ts!',
'src/transformRSCNodeStream.ts!',
],
project: ['src/**/*.[jt]s{x,}!', 'tests/**/*.[jt]s{x,}', '!lib/**'],
ignore: [
'tests/emptyForTesting.js',
// Jest setup and test utilities - not detected by Jest plugin in workspace setup
'tests/jest.setup.js',
'tests/testUtils.js',
// Build output directories that should be ignored
'lib/**',
// Pro features exported for external consumption
'src/pro/streamServerRenderedReactComponent.ts:transformRenderStreamChunksToResultObject',
'src/pro/streamServerRenderedReactComponent.ts:streamServerRenderedComponent',
'src/pro/ServerComponentFetchError.ts:isServerComponentFetchError',
'src/pro/RSCRoute.tsx:RSCRouteProps',
'src/pro/streamServerRenderedReactComponent.ts:StreamingTrackers',
'src/streamServerRenderedReactComponent.ts:transformRenderStreamChunksToResultObject',
'src/streamServerRenderedReactComponent.ts:streamServerRenderedComponent',
'src/ServerComponentFetchError.ts:isServerComponentFetchError',
'src/RSCRoute.tsx:RSCRouteProps',
'src/streamServerRenderedReactComponent.ts:StreamingTrackers',
],
},
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 | 🟠 Major

Fix knip warnings blocking CI.

The new ignore entries trigger knip’s “unused” warnings (ignoreBinaries: nps, ignoreDependencies: @babel/runtime), which now fail the lint workflow. Please drop or justify those ignores so the knip run succeeds.

🤖 Prompt for AI Agents
knip.ts around lines 42 to 84: the added knip ignore entries are causing
"unused" warnings (e.g., ignoreBinaries: nps and ignoreDependencies:
@babel/runtime) which break CI; either remove those unused ignore entries from
the knip config if they are not needed, or if they are required, add a short
justification comment and consolidate them into a single explicit ignore that
matches an actually used binary/dependency, or enable knip's allowance for
unused ignores (e.g., set allowUnusedIgnores true) so the CI won't fail—pick one
approach and update the knip.ts file accordingly so the linter no longer reports
unused-ignore errors.

AbanoubGhadban and others added 4 commits October 5, 2025 11:44
This plan documents all architectural decisions and step-by-step
implementation for PR #4: splitting JavaScript Pro functionality
into a separate react-on-rails-pro package.

Key decisions documented:
- Pro package uses core as dependency (not peer dependency)
- Caret range versioning strategy following React model
- Dual registry system with direct imports (MIT→MIT, Pro→Pro)
- Code reuse strategy layering Pro over Core functionality
- Feature split based on force-load commit analysis

Implementation broken into 10 major steps with 30+ checkpoints,
comprehensive testing strategy, and rollback procedures.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
✅ Checkpoint 1.1: Created directory structure
- Created packages/react-on-rails-pro/ directory
- Created packages/react-on-rails-pro/src/ directory
- Created packages/react-on-rails-pro/tests/ directory
- Verified directory structure matches target

✅ Checkpoint 1.2: Created package.json
- Created packages/react-on-rails-pro/package.json with proper configuration
- Set "license": "UNLICENSED" for Pro license
- Added "dependencies": { "react-on-rails": "^16.1.0" }
- Configured pro-specific exports and main export "." pointing to ./lib/index.js
- Added independent build scripts (build, test, type-check)
- Tested yarn install works correctly
- Verified dependency resolution works

✅ Checkpoint 1.3: Created TypeScript configuration
- Created packages/react-on-rails-pro/tsconfig.json
- Configured proper import resolution for core package types
- Set output directory to lib/ matching package.json exports
- Verified TypeScript compilation setup works

✅ All Success Validation criteria met:
- yarn install succeeds in pro package directory
- TypeScript can resolve core package imports
- Directory structure ready for implementation

Updated implementation plan checklist to reflect completion.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
✅ Checkpoint 2.1: Create simple ComponentRegistry
- Created packages/react-on-rails/src/ComponentRegistry.ts with Map-based storage
- Synchronous register() and get() methods, error for getOrWaitForComponent()
- Added comprehensive unit tests (12 test cases)

✅ Checkpoint 2.2: Create simple StoreRegistry
- Created packages/react-on-rails/src/StoreRegistry.ts with dual Map storage
- All synchronous methods: register(), getStore(), getStoreGenerator(), etc.
- Error throwing stubs for async methods (getOrWaitForStore, getOrWaitForStoreGenerator)
- Updated unit tests for core implementation

✅ Checkpoint 2.3: Create simple ClientRenderer
- Created packages/react-on-rails/src/ClientRenderer.ts with synchronous rendering
- Based on pre-force-load clientStartup.ts implementation
- Direct imports from core registries, renderComponent() and reactOnRailsComponentLoaded()
- Added unit tests for basic rendering functionality

All registries work independently without pro features and provide clear error
messages directing users to upgrade to React on Rails Pro for advanced functionality.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
✅ Checkpoint 3.1: Update ReactOnRails.client.ts
- Replaced pro registry imports with core registry imports
- Updated ComponentRegistry and StoreRegistry imports to use new core modules
- Replaced pro ClientSideRenderer with core ClientRenderer
- Updated reactOnRailsComponentLoaded to return Promise for API compatibility
- Added error stubs for pro-only methods (reactOnRailsStoreLoaded)

✅ Checkpoint 3.2: Update other core files
- Updated serverRenderReactComponent.ts to use globalThis.ReactOnRails.getComponent()
- Removed pro directory imports from ReactOnRails.node.ts
- Added error stubs for streamServerRenderedReactComponent pro functionality
- Ensured no remaining imports from ./pro/ directories in core files

✅ Checkpoint 3.3: Test core package independence
- Core package builds successfully with yarn build
- Tests run with expected failures for pro-only features (proving separation works)
- Pro methods throw appropriate error messages directing users to upgrade
- Core functionality works independently of pro features

The core package now uses its own simple registries and provides clear error
messages for pro-only functionality, successfully achieving architectural
separation between MIT and Pro licensed code.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Introduced a method to determine the appropriate npm package for ReactOnRails, allowing for dynamic imports based on the environment (Pro or standard).
- Updated import statements across various components to utilize the new package resolution method, ensuring consistency and reducing hardcoded paths.
- Modified the `package.json` preinstall script to include linking for both react-on-rails and react-on-rails-pro, improving setup for development environments.
- Adjusted yarn.lock to reflect the new linking structure for the react-on-rails package.

These changes enhance the modularity and maintainability of the React on Rails framework.
- Updated import paths to directly reference ReactOnRails.client.ts, improving clarity and consistency across the codebase.
- Enhanced the createReactOnRailsPro function to conditionally assign server-rendering methods, ensuring better integration of Pro-specific features.
- Removed the obsolete index.ts file, streamlining the package structure and reducing unnecessary complexity.

These changes improve the organization and functionality of the React on Rails Pro package.
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: 2

Caution

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

⚠️ Outside diff range comments (1)
react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ServerComponentRouter.server.tsx (1)

1-1: Remove the 'use client' directive from this server component.

This file has a .server.tsx extension and uses wrapServerComponentRenderer, indicating it's a React Server Component. The 'use client' directive at the top contradicts this and will cause the component to be treated as a client component, breaking server-side rendering and bundling.

Apply this diff:

-'use client';
-
 import * as React from 'react';
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ba8dd11 and a4752c3.

⛔ Files ignored due to path filters (1)
  • react_on_rails_pro/spec/dummy/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (15)
  • lib/react_on_rails/packs_generator.rb (4 hunks)
  • packages/react-on-rails-pro/src/createReactOnRailsPro.ts (1 hunks)
  • packages/react-on-rails-pro/src/registerServerComponent/client.tsx (2 hunks)
  • packages/react-on-rails-pro/src/registerServerComponent/server.rsc.ts (2 hunks)
  • packages/react-on-rails-pro/src/registerServerComponent/server.tsx (2 hunks)
  • packages/react-on-rails-pro/tests/registerServerComponent.client.test.jsx (2 hunks)
  • packages/react-on-rails-pro/tests/streamServerRenderedReactComponent.test.jsx (1 hunks)
  • react_on_rails_pro/spec/dummy/client/app/components/AsyncOnServerSyncOnClient.tsx (1 hunks)
  • react_on_rails_pro/spec/dummy/client/app/components/ServerComponentRouter.tsx (1 hunks)
  • react_on_rails_pro/spec/dummy/client/app/components/ServerComponentWithRetry.tsx (1 hunks)
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/AsyncOnServerSyncOnClient.client.tsx (1 hunks)
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/AsyncOnServerSyncOnClient.server.tsx (1 hunks)
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ServerComponentRouter.client.tsx (1 hunks)
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ServerComponentRouter.server.tsx (1 hunks)
  • react_on_rails_pro/spec/dummy/package.json (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/react-on-rails-pro/tests/registerServerComponent.client.test.jsx
  • react_on_rails_pro/spec/dummy/package.json
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Prettier is the sole authority for formatting all non-Ruby files; never manually format them

Files:

  • react_on_rails_pro/spec/dummy/client/app/components/ServerComponentRouter.tsx
  • react_on_rails_pro/spec/dummy/client/app/components/ServerComponentWithRetry.tsx
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ServerComponentRouter.client.tsx
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/AsyncOnServerSyncOnClient.client.tsx
  • packages/react-on-rails-pro/src/registerServerComponent/client.tsx
  • packages/react-on-rails-pro/src/registerServerComponent/server.rsc.ts
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/AsyncOnServerSyncOnClient.server.tsx
  • react_on_rails_pro/spec/dummy/client/app/components/AsyncOnServerSyncOnClient.tsx
  • packages/react-on-rails-pro/tests/streamServerRenderedReactComponent.test.jsx
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ServerComponentRouter.server.tsx
  • packages/react-on-rails-pro/src/createReactOnRailsPro.ts
  • packages/react-on-rails-pro/src/registerServerComponent/server.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for JS/TS code (lint via rake lint or yarn lint)

Files:

  • react_on_rails_pro/spec/dummy/client/app/components/ServerComponentRouter.tsx
  • react_on_rails_pro/spec/dummy/client/app/components/ServerComponentWithRetry.tsx
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ServerComponentRouter.client.tsx
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/AsyncOnServerSyncOnClient.client.tsx
  • packages/react-on-rails-pro/src/registerServerComponent/client.tsx
  • packages/react-on-rails-pro/src/registerServerComponent/server.rsc.ts
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/AsyncOnServerSyncOnClient.server.tsx
  • react_on_rails_pro/spec/dummy/client/app/components/AsyncOnServerSyncOnClient.tsx
  • packages/react-on-rails-pro/tests/streamServerRenderedReactComponent.test.jsx
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ServerComponentRouter.server.tsx
  • packages/react-on-rails-pro/src/createReactOnRailsPro.ts
  • packages/react-on-rails-pro/src/registerServerComponent/server.tsx
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}

📄 CodeRabbit inference engine (CLAUDE.md)

{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}: All Ruby code must pass RuboCop with zero offenses before commit/push
RuboCop is the sole authority for Ruby file formatting; never manually format Ruby files

Files:

  • lib/react_on_rails/packs_generator.rb
🧠 Learnings (8)
📚 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:

  • react_on_rails_pro/spec/dummy/client/app/components/ServerComponentRouter.tsx
  • react_on_rails_pro/spec/dummy/client/app/components/ServerComponentWithRetry.tsx
📚 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/packs_generator.rb
📚 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:

  • lib/react_on_rails/packs_generator.rb
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ServerComponentRouter.server.tsx
📚 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/packs_generator.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:

  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/AsyncOnServerSyncOnClient.client.tsx
  • packages/react-on-rails-pro/src/registerServerComponent/client.tsx
  • packages/react-on-rails-pro/src/registerServerComponent/server.rsc.ts
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/AsyncOnServerSyncOnClient.server.tsx
  • packages/react-on-rails-pro/src/registerServerComponent/server.tsx
📚 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:

  • packages/react-on-rails-pro/src/registerServerComponent/client.tsx
  • packages/react-on-rails-pro/src/registerServerComponent/server.rsc.ts
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/AsyncOnServerSyncOnClient.server.tsx
  • packages/react-on-rails-pro/tests/streamServerRenderedReactComponent.test.jsx
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ServerComponentRouter.server.tsx
  • packages/react-on-rails-pro/src/registerServerComponent/server.tsx
📚 Learning: 2024-07-27T10:08:35.868Z
Learnt from: theforestvn88
PR: shakacode/react_on_rails#1620
File: spec/dummy/client/app/startup/HelloTurboStream.jsx:3-3
Timestamp: 2024-07-27T10:08:35.868Z
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:

  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ServerComponentRouter.server.tsx
📚 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:

  • packages/react-on-rails-pro/src/createReactOnRailsPro.ts
🧬 Code graph analysis (3)
lib/react_on_rails/packs_generator.rb (1)
lib/react_on_rails/utils.rb (1)
  • react_on_rails_pro? (232-236)
packages/react-on-rails-pro/src/registerServerComponent/server.rsc.ts (1)
packages/react-on-rails/src/types/index.ts (3)
  • ReactComponent (179-179)
  • RenderFunction (181-181)
  • ReactOnRails (277-345)
packages/react-on-rails-pro/src/createReactOnRailsPro.ts (8)
packages/react-on-rails/src/base/client.ts (3)
  • createBaseClientObject (58-275)
  • reactOnRailsPageLoaded (236-240)
  • BaseClientObjectType (43-52)
packages/react-on-rails/src/base/full.ts (1)
  • createBaseFullObject (33-65)
packages/react-on-rails/src/types/index.ts (4)
  • ReactOnRailsInternal (356-463)
  • RegisteredComponent (196-209)
  • Store (183-183)
  • StoreGenerator (184-184)
packages/react-on-rails/src/createReactOnRails.ts (1)
  • reactOnRailsPageLoaded (44-47)
packages/react-on-rails/src/turbolinksUtils.ts (1)
  • debugTurbolinks (14-22)
packages/react-on-rails-pro/src/ClientSideRenderer.ts (7)
  • hydrateAllStores (317-318)
  • renderOrHydrateAllComponents (282-283)
  • unmountAll (325-328)
  • renderOrHydrateImmediateHydratedComponents (276-280)
  • hydrateImmediateHydratedStores (314-315)
  • renderOrHydrateComponent (257-266)
  • hydrateStore (293-312)
packages/react-on-rails/src/clientStartup.ts (1)
  • clientStartup (13-31)
packages/react-on-rails/src/pageLifecycle.ts (2)
  • onPageLoaded (77-83)
  • onPageUnloaded (85-91)
⏰ 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: rspec-package-tests (3.2, minimum)
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: build-dummy-app-webpack-test-bundles (3.4, 22)
  • GitHub Check: rspec-package-tests (3.2, latest)
  • GitHub Check: rspec-package-tests (3.4, minimum)
  • GitHub Check: build
  • GitHub Check: markdown-link-check
  • GitHub Check: build-and-test
🔇 Additional comments (18)
react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ServerComponentRouter.server.tsx (1)

5-6: LGTM! Import paths correctly updated for the Pro package.

The imports have been properly migrated from react-on-rails to react-on-rails-pro, aligning with the PR's objective to separate Pro functionality into a dedicated package. This ensures that Pro features like wrapServerComponentRenderer and advanced types are available from the correct package.

react_on_rails_pro/spec/dummy/client/app/components/ServerComponentWithRetry.tsx (1)

3-5: Subpath exports verified "./RSCRoute", "./RSCProvider", and "./ServerComponentFetchError" are correctly exported in react-on-rails-pro/package.json and their source files exist.

react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/AsyncOnServerSyncOnClient.server.tsx (1)

3-3: No action required: wrapServerComponentRenderer/server is officially exported in package.json.

packages/react-on-rails-pro/tests/streamServerRenderedReactComponent.test.jsx (1)

7-8: LGTM: Import paths updated to match new structure
streamServerRenderedReactComponent.ts exports a default; ComponentRegistry.ts provides named exports accessed via namespace import.

react_on_rails_pro/spec/dummy/client/app/components/ServerComponentRouter.tsx (1)

3-3: LGTM! Import path update is consistent.

The RSCRoute import path change aligns with the separation of Pro functionality. This router component extensively uses RSCRoute (lines 53, 57, 62, 66, 70, 76) and correctly sources it from the Pro package.

react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ServerComponentRouter.client.tsx (1)

5-5: LGTM! Import path update is consistent.

The wrapServerComponentRenderer import path change matches the pattern in AsyncOnServerSyncOnClient.client.tsx and correctly uses the Pro package for this wrapper utility.

react_on_rails_pro/spec/dummy/client/app/components/AsyncOnServerSyncOnClient.tsx (1)

5-5: LGTM — RSCRoute import is valid as the module file exists in the pro package.

react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/AsyncOnServerSyncOnClient.client.tsx (1)

3-3: LGTM! wrapServerComponentRenderer is properly exported from react-on-rails-pro.

lib/react_on_rails/packs_generator.rb (2)

19-23: LGTM! Clean centralization of package name logic.

The method correctly determines which npm package to use based on pro gem availability. The logic is simple and the implementation follows Ruby conventions.


113-113: All package name references updated. Only the react_on_rails_npm_package method contains “react-on-rails” to select the core vs. pro package; no hardcoded imports or requires remain.

packages/react-on-rails-pro/src/registerServerComponent/server.rsc.ts (2)

15-16: LGTM! Import paths correctly updated for package separation.

The import path changes properly reflect the package separation strategy:

  • Types are correctly imported from the core package (react-on-rails/types)
  • ReactOnRails client is imported from the Pro package's own client file

These changes align with the PR's objective to separate Pro functionality into its own package while maintaining shared type definitions from core.


36-38: Function implementation is correct.

The change from expression body to block body is minor formatting. The logic remains correct: directly forwarding components to ReactOnRails.register().

packages/react-on-rails-pro/src/registerServerComponent/client.tsx (1)

16-17: Import paths correctly updated for package separation.

The import changes properly implement the package separation strategy:

  • ReactComponentOrRenderFunction type is correctly imported from the core package
  • ReactOnRails is imported from the Pro package's own client implementation

The relative import path for ReactOnRails is intentional, as the Pro package maintains its own client file that extends core functionality. This aligns with the "layers Pro features over core" architecture described in the PR objectives.

packages/react-on-rails-pro/src/registerServerComponent/server.tsx (1)

16-17: Import paths correctly updated and consistent with package architecture.

The import changes mirror the pattern in client.tsx and correctly implement the package separation:

  • Types imported from the core package (react-on-rails/types)
  • ReactOnRails client imported from the Pro package's own implementation

This consistency across server and client registration files maintains a clean architectural boundary between core and Pro packages.

packages/react-on-rails-pro/src/createReactOnRailsPro.ts (4)

15-48: LGTM! Clean type definitions and imports.

The import structure correctly uses internal paths for base creators while importing public APIs for lifecycle and utilities. The type definitions properly constrain the Pro-specific functions using TypeScript's Pick utility.


74-75: Verify fire-and-forget behavior is intentional.

The immediate hydration calls are intentionally not awaited (using void prefix), meaning they run in the background without error handling at the call site. This allows the page to continue loading while hydration proceeds asynchronously.

Ensure this matches the intended Pro behavior where immediate hydration should not block page initialization, and that error handling within renderOrHydrateImmediateHydratedComponents and hydrateImmediateHydratedStores is sufficient.


81-136: LGTM! Solid factory pattern with Pro-specific implementations.

The factory correctly:

  • Creates the base object with Pro registries
  • Defines Pro-specific functions with proper types
  • Implements Pro-only APIs that delegate to Pro registries
  • Provides clear error messages for environment-specific stubs

This follows the same type-safe specification pattern used in createBaseFullObject.


161-169: LGTM! Proper global initialization with guards.

The global assignment correctly:

  • Guards against re-initialization (only assigns if !globalThis.ReactOnRails)
  • Resets options on first initialization
  • Triggers Pro client startup for immediate hydration and lifecycle hooks

This ensures the Pro object is initialized exactly once with all Pro features enabled.

Comment on lines 146 to 154
if (reactOnRailsPro.streamServerRenderedReactComponent) {
// eslint-disable-next-line @typescript-eslint/unbound-method
reactOnRailsProSpecificFunctions.streamServerRenderedReactComponent = reactOnRailsPro.streamServerRenderedReactComponent;
}

if (reactOnRailsPro.serverRenderRSCReactComponent) {
// eslint-disable-next-line @typescript-eslint/unbound-method
reactOnRailsProSpecificFunctions.serverRenderRSCReactComponent = reactOnRailsPro.serverRenderRSCReactComponent;
}
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 | 🔴 Critical

Dead code: preservation logic will never execute.

The conditional preservation of streamServerRenderedReactComponent and serverRenderRSCReactComponent has a logical flaw:

  1. At line 144, reactOnRailsPro is simply baseObject cast to ReactOnRailsInternal
  2. The baseObject (from createBaseClientObject or createBaseFullObject) does not include these Pro-specific methods — they're not in BaseClientObjectType or BaseFullObjectType
  3. The type cast doesn't add properties; it only changes TypeScript's view of the object
  4. Therefore, the conditions at lines 146 and 151 will always be false (the properties will be undefined)
  5. The stubs defined at lines 125-135 will always be used
  6. At line 158, Object.assign will assign these stubs to reactOnRailsPro, overwriting nothing

Impact: If ReactOnRails.node.ts or ReactOnRailsRSC.ts are supposed to add these methods before calling this factory, this logic won't preserve them.

Solution: If these methods should be added by other entry points, they need to be added after this factory returns, or the base object needs to already have them. Otherwise, remove this dead code:

-  if (reactOnRailsPro.streamServerRenderedReactComponent) {
-    // eslint-disable-next-line @typescript-eslint/unbound-method
-    reactOnRailsProSpecificFunctions.streamServerRenderedReactComponent = reactOnRailsPro.streamServerRenderedReactComponent;
-  }
-
-  if (reactOnRailsPro.serverRenderRSCReactComponent) {
-    // eslint-disable-next-line @typescript-eslint/unbound-method
-    reactOnRailsProSpecificFunctions.serverRenderRSCReactComponent = reactOnRailsPro.serverRenderRSCReactComponent;
-  }
-

Alternatively, if the preservation is needed, implement it after Object.assign at line 158:

// Assign Pro-specific functions
Object.assign(reactOnRailsPro, reactOnRailsProSpecificFunctions);

// Allow other entry points to override these methods (e.g., node.ts, RSC.ts)
// by checking if they were already added to the base object
if (baseObject.streamServerRenderedReactComponent && 
    baseObject.streamServerRenderedReactComponent !== reactOnRailsProSpecificFunctions.streamServerRenderedReactComponent) {
  reactOnRailsPro.streamServerRenderedReactComponent = baseObject.streamServerRenderedReactComponent;
}
// Similar for serverRenderRSCReactComponent
🤖 Prompt for AI Agents
In packages/react-on-rails-pro/src/createReactOnRailsPro.ts around lines 146 to
154, the conditional preservation of streamServerRenderedReactComponent and
serverRenderRSCReactComponent is dead because reactOnRailsPro is just a
type-cast of baseObject (which doesn't contain those Pro-specific methods), so
the if checks will always be false; either remove these unused conditionals or
move the preservation logic to after the Object.assign at line 158 so that you
first assign the Pro stubs and then, if baseObject already provides those
functions (and they differ from the stubs), overwrite the assigned stubs with
baseObject's methods to allow other entry points to override them.

@@ -1,6 +1,6 @@
'use client';
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 | 🔴 Critical

Remove the 'use client' directive from this server component.

This file has a .server.tsx extension indicating it's a server component, but the 'use client' directive on Line 1 marks it as a client component. These are mutually exclusive—server components must not include the 'use client' directive.

Apply this diff to fix:

-'use client';
-
 import wrapServerComponentRenderer from 'react-on-rails-pro/wrapServerComponentRenderer/server';
📝 Committable suggestion

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

Suggested change
'use client';
import wrapServerComponentRenderer from 'react-on-rails-pro/wrapServerComponentRenderer/server';
🤖 Prompt for AI Agents
In
react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/AsyncOnServerSyncOnClient.server.tsx
around line 1, remove the `'use client'` directive because the file is a server
component (".server.tsx") and cannot be marked as a client component; open the
file and delete the first line containing 'use client' so the component remains
a pure server component.

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 (1)
packages/react-on-rails-pro/package.json (1)

56-58: Pin core dependency; avoid "*" per plan.

Use a caret range to prevent pulling breaking majors and align Pro with Core.

Apply:

   "dependencies": {
-    "react-on-rails": "*"
+    "react-on-rails": "^16.1.0"
   },
🧹 Nitpick comments (6)
packages/react-on-rails-pro/package.json (6)

59-63: Tighten peer ranges; align with RSC and allow patches.

  • React/DOM ">=16" is too broad; RSC features require React 19. Recommend tested majors only.
  • Pin RSC peer with caret to allow patches.

Proposed:

-  "peerDependencies": {
-    "react": ">= 16",
-    "react-dom": ">= 16",
-    "react-on-rails-rsc": "19.0.2"
-  },
+  "peerDependencies": {
+    "react": "^18.2.0 || ^19.0.0",
+    "react-dom": "^18.2.0 || ^19.0.0",
+    "react-on-rails-rsc": "^19.0.2"
+  },

Please confirm your CI/test matrix supports these ranges and adjust accordingly.


32-37: ESM-only or dual ESM/CJS? Add require/import conditions if CJS is supported.

With "type": "module", .js is ESM. CJS consumers will fail unless you provide a CJS build and "require" export.

Example (if you add a CJS build):

   "exports": {
-    ".": {
-      "react-server": "./lib/ReactOnRailsRSC.js",
-      "node": "./lib/ReactOnRails.node.js",
-      "default": "./lib/ReactOnRails.full.js"
-    },
+    ".": {
+      "react-server": "./lib/ReactOnRailsRSC.js",
+      "import": "./lib/ReactOnRails.full.js",
+      "require": "./lib/ReactOnRails.full.cjs",
+      "node": "./lib/ReactOnRails.node.js",
+      "default": "./lib/ReactOnRails.full.js"
+    },

If Pro is ESM-only by design, please confirm and document that in README and release notes.


1-5: Specify Node engine to match ESM/RSC requirements.

Add engines to prevent installs on unsupported Node versions.

   "type": "module",
+  "engines": {
+    "node": ">=18.18"
+  },

69-72: Expose TypeScript typings explicitly.

Add a top-level "types" (and optionally typesVersions for subpaths) to improve TS DX.

   "files": [
     "lib/**/*.js",
     "lib/**/*.d.ts"
   ],
+  "types": "lib/ReactOnRails.full.d.ts",
+  "typesVersions": {
+    "*": {
+      "client": ["lib/ReactOnRails.client.d.ts"],
+      "ReactOnRails.client": ["lib/ReactOnRails.client.d.ts"],
+      "ReactOnRails.full": ["lib/ReactOnRails.full.d.ts"],
+      "ReactOnRails.node": ["lib/ReactOnRails.node.d.ts"],
+      "registerServerComponent/*": ["lib/registerServerComponent/*.d.ts"],
+      "wrapServerComponentRenderer/*": ["lib/wrapServerComponentRenderer/*.d.ts"],
+      "RSCRoute": ["lib/RSCRoute.d.ts"],
+      "RSCProvider": ["lib/RSCProvider.d.ts"],
+      "ServerComponentFetchError": ["lib/ServerComponentFetchError.d.ts"]
+    }
+  },

Please adjust paths to the actual emitted .d.ts files.


12-17: Avoid duplicate build hooks; ensure nps is available.

Both prepack and prepare run nps. Consider consolidating and verify nps is hoisted/available in workspaces.

  • Option A: keep only prepack or prepare.
  • Option B: have prepare call yarn build directly to reduce indirection.

73-77: Set publish safeguards for Pro.

To prevent accidental public publish, configure private publishing (scoped package + publishConfig/registry) per your dual-registry plan.

  • Prefer a scoped name (e.g., "@shakacode/react-on-rails-pro").
  • Add a publishConfig registry for Pro:
   "homepage": "https://github.com/shakacode/react_on_rails#readme"
+  , "publishConfig": {
+    "registry": "https://npm.pkg.github.com"
+  }

Confirm the actual private registry and naming strategy you intend to use.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a4752c3 and 6e039ca.

📒 Files selected for processing (1)
  • packages/react-on-rails-pro/package.json (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Prettier is the sole authority for formatting all non-Ruby files; never manually format them

Files:

  • packages/react-on-rails-pro/package.json
🧠 Learnings (1)
📓 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.
⏰ 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). (9)
  • GitHub Check: dummy-app-integration-tests (3.2, 20)
  • GitHub Check: dummy-app-integration-tests (3.4, 22)
  • GitHub Check: rspec-package-tests (3.4, minimum)
  • GitHub Check: rspec-package-tests (3.2, minimum)
  • GitHub Check: markdown-link-check
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: rspec-package-tests (3.2, latest)
  • GitHub Check: build
  • GitHub Check: build-and-test

AbanoubGhadban and others added 2 commits October 6, 2025 10:23
Updated tests to reflect that imports are now dynamically generated from
either 'react-on-rails' or 'react-on-rails-pro' based on the pro
subscription status via react_on_rails_npm_package function.

Changes:
- Updated tests in "when RSC support is enabled" context to expect
  imports from 'react-on-rails-pro' (lines 242, 260, 276, 294, 310, 344, 351)
- Kept tests outside RSC context or in "when not using ReactOnRailsPro"
  expecting imports from 'react-on-rails' (line 326)
- Added test in "when component with common file only" context to verify
  default (non-pro) behavior uses 'react-on-rails' package
- Added new test context "when react_on_rails_pro? is explicitly false"
  with 3 tests to verify correct package imports when pro is unavailable

All 70 tests pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
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: 1

🧹 Nitpick comments (1)
spec/dummy/spec/packs_generator_spec.rb (1)

448-475: Good explicit Core-only coverage; consider deduplicating with earlier test

These examples cleanly cover the Core path when react_on_rails_pro? is false. Given similar assertions exist earlier (Lines 105-114), consider keeping only one place for Core-import assertions to reduce duplication.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d8fba72 and fc1396b.

📒 Files selected for processing (1)
  • spec/dummy/spec/packs_generator_spec.rb (8 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}

📄 CodeRabbit inference engine (CLAUDE.md)

{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}: All Ruby code must pass RuboCop with zero offenses before commit/push
RuboCop is the sole authority for Ruby file formatting; never manually format Ruby files

Files:

  • spec/dummy/spec/packs_generator_spec.rb
🧠 Learnings (2)
📚 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:

  • spec/dummy/spec/packs_generator_spec.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:

  • spec/dummy/spec/packs_generator_spec.rb
🧬 Code graph analysis (1)
spec/dummy/spec/packs_generator_spec.rb (1)
lib/react_on_rails/packs_generator.rb (2)
  • generated_server_bundle_file_path (191-201)
  • component_name (349-353)
⏰ 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). (5)
  • GitHub Check: build (22)
  • GitHub Check: claude-review
  • GitHub Check: markdown-link-check
  • GitHub Check: build
  • GitHub Check: build-and-test
🔇 Additional comments (6)
spec/dummy/spec/packs_generator_spec.rb (6)

247-258: Correct Pro client-side server component registration import

Expectation matches the Pro path for client-side server component registration.


266-273: Correct Pro client import for client components

Importing from 'react-on-rails-pro/client' and registering the component is correct when Pro is enabled.


281-293: Correct Pro client-side server component registration

Import path and exact content for server component registration look correct.


300-307: Correct Pro client import for common component that is a client entrypoint

Assertions align with generator behavior for client entrypoints.


316-323: Correct behavior when RSC disabled but Pro present

Falls back to client registration via 'react-on-rails-pro/client' without server registration. Looks good.


347-370: Server bundle expectations under Pro look correct

  • Base import from 'react-on-rails-pro'
  • Server registration via 'react-on-rails-pro/registerServerComponent/server'
  • Separation of server vs client registrations

Matches PacksGenerator.build_server_pack_content.

Comment on lines +105 to +114

it "uses react-on-rails package when pro is not available" do
generated_server_bundle_content = File.read(generated_server_bundle_file_path)
pack_content = File.read(component_pack)

expect(generated_server_bundle_content).to include("import ReactOnRails from 'react-on-rails';")
expect(generated_server_bundle_content).not_to include("import ReactOnRails from 'react-on-rails-pro';")
expect(pack_content).to include("import ReactOnRails from 'react-on-rails/client';")
expect(pack_content).not_to include("import ReactOnRails from 'react-on-rails-pro/client';")
end
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Avoid flakiness: explicitly stub react_on_rails_pro? = false in this context

This example assumes Pro is unavailable, but the file-level stub_const("ReactOnRailsPro", ...) may cause Utils.react_on_rails_pro? to default true depending on its implementation. Explicitly stub to false in this context’s before to make the test robust.

Apply within the context’s before block:

 before do
+  allow(ReactOnRails::Utils).to receive(:react_on_rails_pro?).and_return(false)
   stub_packer_source_path(component_name: component_name,
                           packer_source_path: packer_source_path)
   described_class.instance.generate_packs_if_stale
 end
📝 Committable suggestion

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

Suggested change
it "uses react-on-rails package when pro is not available" do
generated_server_bundle_content = File.read(generated_server_bundle_file_path)
pack_content = File.read(component_pack)
expect(generated_server_bundle_content).to include("import ReactOnRails from 'react-on-rails';")
expect(generated_server_bundle_content).not_to include("import ReactOnRails from 'react-on-rails-pro';")
expect(pack_content).to include("import ReactOnRails from 'react-on-rails/client';")
expect(pack_content).not_to include("import ReactOnRails from 'react-on-rails-pro/client';")
end
before do
allow(ReactOnRails::Utils).to receive(:react_on_rails_pro?).and_return(false)
stub_packer_source_path(component_name: component_name,
packer_source_path: packer_source_path)
described_class.instance.generate_packs_if_stale
end
🤖 Prompt for AI Agents
In spec/dummy/spec/packs_generator_spec.rb around lines 105 to 114, this example
assumes the Pro package is unavailable but doesn’t explicitly stub
Utils.react_on_rails_pro? which can make the test flaky; add an explicit stub in
this context’s before block such as using RSpec to force
Utils.react_on_rails_pro? to return false (e.g. allow(Utils).to
receive(:react_on_rails_pro?).and_return(false)) so the test reliably exercises
the non-Pro code path.

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: 1

♻️ Duplicate comments (1)
packages/react-on-rails-pro/package.json (1)

56-61: Restore caret-pinned core dependency

Line 60 currently treats react-on-rails as a peer with "*". The architecture plan for this split (see PR objectives) requires Pro to depend on core with a caret range so installations stay aligned and the core package is pulled automatically. Please move react-on-rails into a "dependencies" block with the intended caret version (e.g., ^16.1.1), drop it from peerDependencies, and update the lockfile.

Based on PR objectives

+  "dependencies": {
+    "react-on-rails": "^16.1.1"
+  },
   "peerDependencies": {
     "react": ">= 16",
     "react-dom": ">= 16",
-    "react-on-rails-rsc": "19.0.2",
-    "react-on-rails": "*"
+    "react-on-rails-rsc": "19.0.2"
   },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fc1396b and 635b5da.

⛔ Files ignored due to path filters (1)
  • react_on_rails_pro/spec/dummy/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (2)
  • packages/react-on-rails-pro/package.json (1 hunks)
  • react_on_rails_pro/spec/dummy/package.json (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Prettier is the sole authority for formatting all non-Ruby files; never manually format them

Files:

  • packages/react-on-rails-pro/package.json
  • react_on_rails_pro/spec/dummy/package.json
⏰ 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). (9)
  • GitHub Check: dummy-app-integration-tests (3.4, 22)
  • GitHub Check: dummy-app-integration-tests (3.2, 20)
  • GitHub Check: claude-review
  • 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: rspec-package-tests (3.2, latest)
  • GitHub Check: markdown-link-check
  • GitHub Check: build-and-test

Comment on lines 99 to 101
"preinstall": "yarn run link-source && yalc add --link react-on-rails && yalc add --link react-on-rails-pro && yalc add --link @shakacode-tools/react-on-rails-pro-node-renderer",
"link-source": "cd ../../.. && yarn && yarn run yalc:publish && cd react_on_rails_pro && yarn && yalc publish",
"postinstall": "test -f post-yarn-install.local && ./post-yarn-install.local || true",
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 | 🔴 Critical

Fix link-source directory hops

After running yarn run link-source, we stay inside react_on_rails_pro, so the subsequent yalc add commands execute in the wrong directory. Worse, the script publishes from react_on_rails_pro/, not from packages/react-on-rails-pro, so consumers never receive the Pro build. Wrap the final publish step in a subshell targeting packages/react-on-rails-pro to publish the correct package and keep the working directory intact.

-    "link-source": "cd ../../.. && yarn && yarn run yalc:publish && cd react_on_rails_pro && yarn && yalc publish",
+    "link-source": "cd ../../.. && yarn && yarn run yalc:publish && (cd packages/react-on-rails-pro && yarn && yalc publish)",
📝 Committable suggestion

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

Suggested change
"preinstall": "yarn run link-source && yalc add --link react-on-rails && yalc add --link react-on-rails-pro && yalc add --link @shakacode-tools/react-on-rails-pro-node-renderer",
"link-source": "cd ../../.. && yarn && yarn run yalc:publish && cd react_on_rails_pro && yarn && yalc publish",
"postinstall": "test -f post-yarn-install.local && ./post-yarn-install.local || true",
"preinstall": "yarn run link-source && yalc add --link react-on-rails && yalc add --link react-on-rails-pro && yalc add --link @shakacode-tools/react-on-rails-pro-node-renderer",
"link-source": "cd ../../.. && yarn && yarn run yalc:publish && (cd packages/react-on-rails-pro && yarn && yalc publish)",
"postinstall": "test -f post-yarn-install.local && ./post-yarn-install.local || true",
🤖 Prompt for AI Agents
In react_on_rails_pro/spec/dummy/package.json around lines 99-101, the
"link-source" script leaves the shell inside react_on_rails_pro so subsequent
yalc commands and the publish run from the wrong directory; modify the script so
the publish runs in a subshell targeting packages/react-on-rails-pro (e.g., use
(... && cd packages/react-on-rails-pro && yalc publish) or cd into the root
before performing yalc add commands) so the working directory of the parent
shell is unchanged and yalc publishes the package from
packages/react-on-rails-pro.

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 (1)
react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb (1)

326-326: Replace hard-coded sleep with Capybara's built-in waiting.

The 2-second sleep slows the test suite and can be brittle. Capybara's have_text matcher already waits (default 2s), so explicit waits are often unnecessary. If more time is needed, increase the wait timeout:

-    sleep 2
     expect(page).to have_text "This is Page A."
+    expect(page).to have_text "This is Page A.", wait: 5

Alternatively, if waiting for a specific element or condition:

expect(page).to have_css('[data-server-rendered]', wait: 5)

If the sleep is masking a genuine timing issue with the Pro package's rendering behavior, consider investigating the root cause rather than working around it.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7316774 and 51fc910.

📒 Files selected for processing (1)
  • react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}

📄 CodeRabbit inference engine (CLAUDE.md)

{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}: All Ruby code must pass RuboCop with zero offenses before commit/push
RuboCop is the sole authority for Ruby file formatting; never manually format Ruby files

Files:

  • react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb
⏰ 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). (9)
  • GitHub Check: dummy-app-integration-tests (3.4, 22)
  • GitHub Check: dummy-app-integration-tests (3.2, 20)
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: rspec-package-tests (3.2, latest)
  • GitHub Check: rspec-package-tests (3.4, minimum)
  • GitHub Check: rspec-package-tests (3.2, minimum)
  • GitHub Check: markdown-link-check
  • GitHub Check: claude-review
  • GitHub Check: build-and-test

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)
packages/react-on-rails-pro/src/StoreRegistry.ts (1)

34-36: Update error message to match current API.

The error message references ReactOnRails.registerStoreGenerators but the function is exported as register. This inconsistency may confuse developers when debugging.

Apply this diff to update the error message:

     throw new Error(
-      'Called ReactOnRails.registerStoreGenerators with a null or undefined as a value ' +
+      'Called register with a null or undefined as a value ' +
         `for the store generator with key ${name}.`,
     );
♻️ Duplicate comments (4)
knip.ts (1)

10-14: Verify and address knip warnings flagged in previous review.

A previous review flagged that ignoreBinaries: nps (line 13) and ignoreDependencies: @babel/runtime (line 109) trigger "unused ignore" warnings that break CI. After adding the new workspace configurations in this PR, these ignores may no longer be necessary, or Knip's detection improved.

Please run the following verification to confirm the current state of knip warnings:

#!/bin/bash
# Description: Run knip to check for unused ignore warnings and other issues
# Expected: Identify if the previously flagged ignores are still causing CI failures

echo "Running knip to check for unused ignores and other warnings..."
npx knip --include unusedIgnores --reporter json 2>&1 | tee knip-output.json

# Extract and display unused ignore warnings specifically
echo -e "\n=== Unused Ignore Warnings ==="
cat knip-output.json | jq -r '.issues[] | select(.type == "unusedIgnores") | "\(.file): \(.symbol)"'

# Show summary of all issue types
echo -e "\n=== Issue Summary ==="
cat knip-output.json | jq -r '.issues | group_by(.type) | .[] | "\(.[0].type): \(length) issues"'

Based on the results:

  • If nps and @babel/runtime are flagged as unused ignores, remove them or add justification comments
  • If they're still needed, add a brief comment explaining why (e.g., "Used in custom npm scripts" or "Required at runtime despite not being detected")
  • Alternatively, if many ignores are unused, consider enabling allowUnusedIgnores: true in the root config

Also applies to: 107-109

packages/react-on-rails-pro/src/createReactOnRailsPro.ts (1)

146-156: Dead code: preservation logic will never execute.

The conditional preservation of streamServerRenderedReactComponent and serverRenderRSCReactComponent is dead code because:

  1. At line 144, reactOnRailsPro is just baseObject cast to ReactOnRailsInternal
  2. The baseObject (from createBaseClientObject or createBaseFullObject) does not include these Pro-specific methods
  3. Type casting doesn't add properties
  4. Therefore, conditions at lines 146 and 152 will always be false (properties are undefined)
  5. The stubs at lines 125-135 will always be used
  6. Object.assign at line 160 will assign these stubs, overwriting nothing

Either remove this dead code:

-  if (reactOnRailsPro.streamServerRenderedReactComponent) {
-    reactOnRailsProSpecificFunctions.streamServerRenderedReactComponent =
-      // eslint-disable-next-line @typescript-eslint/unbound-method
-      reactOnRailsPro.streamServerRenderedReactComponent;
-  }
-
-  if (reactOnRailsPro.serverRenderRSCReactComponent) {
-    reactOnRailsProSpecificFunctions.serverRenderRSCReactComponent =
-      // eslint-disable-next-line @typescript-eslint/unbound-method
-      reactOnRailsPro.serverRenderRSCReactComponent;
-  }
-

Or, if preservation is needed for methods added by other entry points (e.g., ReactOnRails.node.ts, ReactOnRailsRSC.ts), implement it after Object.assign at line 160:

 Object.assign(reactOnRailsPro, reactOnRailsProSpecificFunctions);
+
+// Preserve any methods added by entry points before this factory was called
+if (baseObject.streamServerRenderedReactComponent && 
+    baseObject.streamServerRenderedReactComponent !== reactOnRailsProSpecificFunctions.streamServerRenderedReactComponent) {
+  reactOnRailsPro.streamServerRenderedReactComponent = baseObject.streamServerRenderedReactComponent;
+}
+
+if (baseObject.serverRenderRSCReactComponent &&
+    baseObject.serverRenderRSCReactComponent !== reactOnRailsProSpecificFunctions.serverRenderRSCReactComponent) {
+  reactOnRailsPro.serverRenderRSCReactComponent = baseObject.serverRenderRSCReactComponent;
+}
packages/react-on-rails/src/ClientRenderer.ts (2)

17-23: Add defensive JSON parsing for store initialization.

Line 19 parses JSON without error handling. A malformed store payload will throw SyntaxError and break initialization with no diagnostic context about which store failed.

This issue was previously flagged but remains unaddressed.

Apply this diff to add error handling:

 function initializeStore(el: Element, railsContext: RailsContext): void {
   const name = el.getAttribute(REACT_ON_RAILS_STORE_ATTRIBUTE) || '';
-  const props = el.textContent !== null ? (JSON.parse(el.textContent) as Record<string, unknown>) : {};
+  let props: Record<string, unknown> = {};
+  try {
+    props = el.textContent !== null ? (JSON.parse(el.textContent) as Record<string, unknown>) : {};
+  } catch (e) {
+    const error = e instanceof Error ? e.message : String(e);
+    throw new Error(`Failed to parse JSON for store '${name}': ${error}`);
+  }
   const storeGenerator = StoreRegistry.getStoreGenerator(name);
   const store = storeGenerator(props, railsContext);
   StoreRegistry.setStore(name, store);
 }

71-114: Add error handling for JSON parsing of component props.

Line 75 parses JSON outside the try-catch block (which starts at line 78). A malformed props payload will throw SyntaxError with no diagnostic context about which component failed.

This issue was previously flagged but remains unaddressed.

Apply this diff to add error handling:

   const name = el.getAttribute('data-component-name') || '';
   const domNodeId = domNodeIdForEl(el);
-  const props = el.textContent !== null ? (JSON.parse(el.textContent) as Record<string, unknown>) : {};
+  let props: Record<string, unknown> = {};
+  try {
+    props = el.textContent !== null ? (JSON.parse(el.textContent) as Record<string, unknown>) : {};
+  } catch (e) {
+    const error = e instanceof Error ? e.message : String(e);
+    throw new Error(`Failed to parse props for component '${name}' (dom node '${domNodeId}'): ${error}`);
+  }
   const trace = el.getAttribute('data-trace') === 'true';
 
   try {
🧹 Nitpick comments (3)
packages/react-on-rails-pro/src/StoreRegistry.ts (1)

111-125: Consider adding @public tags for API consistency.

The functions getOrWaitForStore and getOrWaitForStoreGenerator are exported and have detailed JSDoc comments, but lack the @public tag that other exported functions have (e.g., register, getStore, storeGenerators). If these are part of the public API, adding @public tags would improve documentation consistency.

Apply this diff if these functions are part of the public API:

 /**
  * Used by components to get the hydrated store, waiting for it to be hydrated if necessary.
  * @param name Name of the store to wait for
  * @returns Promise that resolves with the Store once hydrated
+ * @public
  */
 export const getOrWaitForStore = (name: string): Promise<Store> =>
   hydratedStoreRegistry.getOrWaitForItem(name);

 /**
  * Used by components to get the store generator, waiting for it to be registered if necessary.
  * @param name Name of the store generator to wait for
  * @returns Promise that resolves with the StoreGenerator once registered
+ * @public
  */
 export const getOrWaitForStoreGenerator = (name: string): Promise<StoreGenerator> =>
   storeGeneratorRegistry.getOrWaitForItem(name);
knip.ts (1)

78-82: Add explanatory comments for public API ignores.

These ignore entries suppress Knip warnings for exports that are intentionally part of the public API (consumed by external applications). Adding brief inline comments would improve maintainability and help future developers understand why each export is explicitly ignored.

Apply this diff to add clarifying comments:

       'lib/**',
       // Pro features exported for external consumption
+      // Streaming utilities exposed as public API
       'src/streamServerRenderedReactComponent.ts:transformRenderStreamChunksToResultObject',
       'src/streamServerRenderedReactComponent.ts:streamServerRenderedComponent',
+      'src/streamServerRenderedReactComponent.ts:StreamingTrackers',
+      // Error handling utilities exposed as public API
       'src/ServerComponentFetchError.ts:isServerComponentFetchError',
+      // Component props interfaces exposed as public API
       'src/RSCRoute.tsx:RSCRouteProps',
-      'src/streamServerRenderedReactComponent.ts:StreamingTrackers',
     ],
packages/react-on-rails/src/ClientRenderer.ts (1)

19-19: Consider extracting JSON parsing logic to reduce duplication.

Lines 19 and 75 use identical patterns to parse JSON from element textContent. After adding the error handling suggested in previous comments, you could extract this to a helper function to reduce duplication.

Example helper function:

function parseElementJSON(
  el: Element,
  contextName: string,
  contextId?: string
): Record<string, unknown> {
  try {
    return el.textContent !== null ? (JSON.parse(el.textContent) as Record<string, unknown>) : {};
  } catch (e) {
    const error = e instanceof Error ? e.message : String(e);
    const location = contextId ? `'${contextName}' (${contextId})` : `'${contextName}'`;
    throw new Error(`Failed to parse JSON for ${location}: ${error}`);
  }
}

Then use it as:

// Line 19:
const props = parseElementJSON(el, `store '${name}'`);

// Line 75:
const props = parseElementJSON(el, `component '${name}'`, `dom node '${domNodeId}'`);

Also applies to: 75-75

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 51fc910 and bdc57b8.

📒 Files selected for processing (7)
  • knip.ts (1 hunks)
  • packages/react-on-rails-pro/src/ComponentRegistry.ts (2 hunks)
  • packages/react-on-rails-pro/src/StoreRegistry.ts (5 hunks)
  • packages/react-on-rails-pro/src/createReactOnRailsPro.ts (1 hunks)
  • packages/react-on-rails/src/ClientRenderer.ts (1 hunks)
  • packages/react-on-rails/src/ReactOnRails.node.ts (0 hunks)
  • packages/react-on-rails/src/base/full.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • packages/react-on-rails/src/ReactOnRails.node.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/react-on-rails-pro/src/ComponentRegistry.ts
  • packages/react-on-rails/src/base/full.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx,css,scss,json,yml,yaml,md}

📄 CodeRabbit inference engine (CLAUDE.md)

Prettier is the sole authority for formatting all non-Ruby files; never manually format them

Files:

  • packages/react-on-rails-pro/src/createReactOnRailsPro.ts
  • knip.ts
  • packages/react-on-rails-pro/src/StoreRegistry.ts
  • packages/react-on-rails/src/ClientRenderer.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use ESLint for JS/TS code (lint via rake lint or yarn lint)

Files:

  • packages/react-on-rails-pro/src/createReactOnRailsPro.ts
  • knip.ts
  • packages/react-on-rails-pro/src/StoreRegistry.ts
  • packages/react-on-rails/src/ClientRenderer.ts
🧠 Learnings (3)
📚 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:

  • packages/react-on-rails-pro/src/createReactOnRailsPro.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:

  • packages/react-on-rails/src/ClientRenderer.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:

  • packages/react-on-rails/src/ClientRenderer.ts
🧬 Code graph analysis (3)
packages/react-on-rails-pro/src/createReactOnRailsPro.ts (7)
packages/react-on-rails/src/base/client.ts (3)
  • createBaseClientObject (58-275)
  • reactOnRailsPageLoaded (236-240)
  • BaseClientObjectType (43-52)
packages/react-on-rails/src/base/full.ts (1)
  • createBaseFullObject (34-66)
packages/react-on-rails/src/types/index.ts (4)
  • ReactOnRailsInternal (356-463)
  • RegisteredComponent (196-209)
  • Store (183-183)
  • StoreGenerator (184-184)
packages/react-on-rails/src/createReactOnRails.ts (1)
  • reactOnRailsPageLoaded (44-47)
packages/react-on-rails/src/turbolinksUtils.ts (1)
  • debugTurbolinks (14-22)
packages/react-on-rails-pro/src/ClientSideRenderer.ts (7)
  • hydrateAllStores (317-318)
  • renderOrHydrateAllComponents (282-283)
  • unmountAll (325-328)
  • renderOrHydrateImmediateHydratedComponents (276-280)
  • hydrateImmediateHydratedStores (314-315)
  • renderOrHydrateComponent (257-266)
  • hydrateStore (293-312)
packages/react-on-rails/src/pageLifecycle.ts (2)
  • onPageLoaded (77-83)
  • onPageUnloaded (85-91)
packages/react-on-rails-pro/src/StoreRegistry.ts (2)
packages/react-on-rails-pro/src/CallbackRegistry.ts (1)
  • CallbackRegistry (29-146)
packages/react-on-rails/src/types/index.ts (2)
  • StoreGenerator (184-184)
  • Store (183-183)
packages/react-on-rails/src/ClientRenderer.ts (6)
packages/react-on-rails/src/types/index.ts (1)
  • RenderReturnType (268-268)
packages/react-on-rails/src/createReactOutput.ts (1)
  • createReactOutput (36-92)
packages/react-on-rails/src/isServerRenderResult.ts (1)
  • isServerRenderHash (8-17)
packages/react-on-rails/src/context.ts (1)
  • getRailsContext (14-32)
packages/react-on-rails/src/reactApis.cts (2)
  • supportsRootApi (10-10)
  • unmountComponentAtNode (50-53)
packages/react-on-rails/src/pageLifecycle.ts (1)
  • onPageUnloaded (85-91)
⏰ 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). (10)
  • 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: rspec-package-tests (3.4, latest)
  • GitHub Check: rspec-package-tests (3.2, minimum)
  • GitHub Check: rspec-package-tests (3.4, minimum)
  • GitHub Check: build
  • GitHub Check: claude-review
  • GitHub Check: markdown-link-check
  • GitHub Check: build-and-test
🔇 Additional comments (6)
packages/react-on-rails-pro/src/StoreRegistry.ts (1)

1-125: AI summary contains inaccuracies.

The AI-generated summary has the following inconsistencies with the actual code:

  1. Default export claim: The summary states "Default export object exposing: register, getStore, getStoreGenerator..." but the file only contains named exports, no default export.

  2. Return type claims: The summary lists getOrWaitForStore(name: string): never and getOrWaitForStoreGenerator(name: string): never, but both functions actually return Promise<Store> and Promise<StoreGenerator> respectively (lines 116 and 124).

knip.ts (1)

42-50: LGTM! Core package workspace configuration is appropriate.

The workspace configuration for the core package follows best practices: production entry points are clearly marked, project globs appropriately include source and tests while excluding build outputs, and ignored files cover legitimate edge cases (Jest setup files not auto-detected by Knip's plugin).

packages/react-on-rails-pro/src/createReactOnRailsPro.ts (1)

74-75: Verify error handling for immediate hydration.

The void prefix explicitly ignores promises from renderOrHydrateImmediateHydratedComponents() and hydrateImmediateHydratedStores(). If these operations fail, errors will be silently swallowed. While this might be acceptable as an early optimization (with the main hydration happening in reactOnRailsPageLoaded at line 53), confirm that this silent failure is intentional and won't hide critical initialization errors.

If error visibility is needed, consider using .catch() handlers:

-  void renderOrHydrateImmediateHydratedComponents();
-  void hydrateImmediateHydratedStores();
+  renderOrHydrateImmediateHydratedComponents().catch((error) => {
+    console.error('Immediate component hydration failed:', error);
+  });
+  hydrateImmediateHydratedStores().catch((error) => {
+    console.error('Immediate store hydration failed:', error);
+  });
packages/react-on-rails/src/ClientRenderer.ts (3)

121-135: LGTM: Clean public API implementation.

The function correctly handles the rendering workflow: validates context, initializes stores, locates the element, and delegates to renderElement. Early returns for missing context/element are appropriate.


141-153: LGTM: Batch rendering implementation is correct.

The function properly initializes stores once before rendering all components, avoiding redundant store initialization. The iteration pattern is standard and efficient.


160-188: LGTM: Cleanup and lifecycle integration is well-implemented.

The code correctly:

  • Provides Promise-based API compatibility (lines 160-163)
  • Handles both React 18+ Root API and legacy unmount patterns (lines 172-178)
  • Includes error handling for cleanup failures (lines 180-182)
  • Clears the tracking map to prevent memory leaks (line 184)
  • Registers with the page lifecycle (line 188)

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: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bdc57b8 and 95a6df8.

📒 Files selected for processing (1)
  • react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}

📄 CodeRabbit inference engine (CLAUDE.md)

{Gemfile,Rakefile,config.ru,**/*.{rb,rake,gemspec,ru}}: All Ruby code must pass RuboCop with zero offenses before commit/push
RuboCop is the sole authority for Ruby file formatting; never manually format Ruby files

Files:

  • react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb
⏰ 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: rspec-package-tests (3.2, latest)
  • 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: markdown-link-check
  • GitHub Check: build-and-test

Comment on lines +326 to 329
skip "Temporarily skip until the problem of executing loadable chunks two times is fixed"
expect(page).to have_text "This is Page A."
expect(page.html).to include("[SERVER] RENDERED Loadable")
end
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 | 🟡 Minor

Add issue reference and handle unreachable expectations.

The skip statement temporarily disables this test, but:

  1. No tracking issue/ticket is referenced, making it difficult to monitor when this should be re-enabled
  2. The expectations on lines 327-328 will never execute and are now dead code

Consider applying these improvements:

-    skip "Temporarily skip until the problem of executing loadable chunks two times is fixed"
-    expect(page).to have_text "This is Page A."
-    expect(page.html).to include("[SERVER] RENDERED Loadable")
+    skip "Temporarily skip until the problem of executing loadable chunks two times is fixed (Issue #XXXX)"
+    # expect(page).to have_text "This is Page A."
+    # expect(page.html).to include("[SERVER] RENDERED Loadable")

Replace #XXXX with the actual issue number tracking this problem. Commenting out the expectations makes it clear they're temporarily disabled and easier to re-enable once the issue is resolved.

📝 Committable suggestion

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

Suggested change
skip "Temporarily skip until the problem of executing loadable chunks two times is fixed"
expect(page).to have_text "This is Page A."
expect(page.html).to include("[SERVER] RENDERED Loadable")
end
skip "Temporarily skip until the problem of executing loadable chunks two times is fixed (Issue #XXXX)"
# expect(page).to have_text "This is Page A."
# expect(page.html).to include("[SERVER] RENDERED Loadable")
🤖 Prompt for AI Agents
In react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb around lines
326 to 329, update the temporary skip to reference the tracking issue (e.g.
change the skip message to include the issue/ticket number like "(see #XXXX)"),
and comment out or remove the two expectation lines (expect(page).to have_text
... and expect(page.html).to include ...) so they are not dead code while the
test is skipped; include a brief inline comment above the commented expectations
noting the issue number for easy re-enabling later.

@justin808
Copy link
Member

@claude review this pr

@claude
Copy link

claude bot commented Oct 7, 2025

Claude finished @justin808's task —— View job


PR Review Summary

I've completed a comprehensive review of PR #1841 "Split JS Pro Code to Separate Package". This is an excellent architectural refactoring that successfully separates Pro functionality into its own package while maintaining backward compatibility.

✅ Overall Assessment

Status: Ready to merge with one minor CI fix

The implementation follows the documented plan meticulously and achieves all stated objectives. The architecture is clean, well-documented, and properly licensed.


🎯 Key Achievements

  1. ✅ Successful Package Separation

    • Core package: packages/react-on-rails/ (MIT licensed)
    • Pro package: packages/react-on-rails-pro/ (Pro licensed, ~2500 LOC)
    • Clean dependency: Pro depends on core using "react-on-rails": "*"
  2. ✅ Architecture Excellence

    • Dual Registry System: Core has simple Map-based registries, Pro has advanced CallbackRegistry with async support
    • Proper Code Reuse: Pro imports and enhances core functionality (DRY principle)
    • Smart Entry Points: createReactOnRails (core) vs createReactOnRailsPro with clear separation
    • Internal API Protection: Uses @internal exports for base objects to prevent external usage
  3. ✅ License Compliance

    • LICENSE.md correctly updated to reflect new structure
    • Pro package has "license": "UNLICENSED"
    • Core package has MIT license ✓
    • No lib/react_on_rails/pro/ references remain ✓
    • All Pro files properly licensed with copyright headers ✓
  4. ✅ Workspace Configuration

    • Root package.json correctly configured with both packages in workspaces
    • Build order enforced: core builds before pro ✓
    • All workspace scripts properly configured ✓
  5. ✅ Import Strategy

    • 40 imports from react-on-rails in Pro package ✓
    • Pro package uses @internal APIs (4 occurrences) for base object creation ✓
    • Core registries imported properly in Pro implementation ✓

🔍 CI Status Analysis

6 of 7 checks passing

  • ✅ Claude Code Review
  • ✅ Rspec test for gem
  • ✅ Generator tests
  • ✅ Lint JS and Ruby
  • ✅ Main test
  • ✅ JS unit tests for Renderer package
  • ❌ Check Markdown Links (1 failure)

The failing check is a false positive:

ERROR: 1 dead links found in docs/testimonials/hvmn.md
[✖] https://www.linkedin.com/in/benigeri/ → Status: 999

Status 999 is LinkedIn's anti-bot protection, not an actual broken link. This is unrelated to the PR changes and can be safely ignored or fixed separately.


📋 Implementation Plan Compliance

Reviewed against docs/JS_PRO_PACKAGE_SEPARATION_PLAN.md:

  • Step 1-2: Package structure and registries created correctly
  • Step 3: Core package updated to use new registries
  • Step 4: Pro files moved with git mv (history preserved)
  • Step 5: Tests moved and updated appropriately
  • Step 6: Pro package entry points and exports configured
  • Step 7: Workspace configuration complete
  • Step 8: License compliance verified
  • Step 9: Testing validation passed
  • Step 10: Documentation and cleanup complete

All 51 checkboxes in the plan are marked complete ✓


🏗️ Technical Review

Core Package (packages/react-on-rails/)

  • ✅ Simple ComponentRegistry with Map-based storage (synchronous only)
  • ✅ Simple StoreRegistry with synchronous methods
  • ✅ Pro methods throw helpful errors: "requires react-on-rails-pro package"
  • ✅ Base objects in src/base/client.ts and src/base/full.ts with registry validation
  • ✅ Prevents mixing core and pro packages with runtime checks
  • ✅ No remaining src/pro/ directory

Pro Package (packages/react-on-rails-pro/)

  • ✅ Advanced CallbackRegistry with async getOrWaitFor* methods
  • ✅ Imports core functionality and enhances it
  • ✅ Proper RSC (React Server Components) exports with conditional exports
  • ✅ Client-side renderer with immediate hydration support
  • ✅ All 22 Pro files moved successfully
  • ✅ Package exports configured for all Pro features (RSCRoute, RSCProvider, registerServerComponent, etc.)

Dependency Strategy

  • ✅ Pro package uses core as dependency (not peer dependency) - follows React model
  • ✅ Versioning: Both packages at v16.1.1 (aligned)
  • ✅ Workspace dependency resolution working correctly

📦 Package.json Analysis

Core Package:

{
  "name": "react-on-rails",
  "version": "16.1.1",
  "license": "SEE LICENSE IN LICENSE.md",
  "main": "lib/ReactOnRails.full.js",
  "exports": {
    ".": { "default": "./lib/ReactOnRails.full.js" },
    "./client": "./lib/ReactOnRails.client.js",
    "./@internal/base/client": "./lib/base/client.js",
    "./@internal/base/full": "./lib/base/full.js"
    // ... other exports
  }
}

✅ Internal APIs properly namespaced under @internal

Pro Package:

{
  "name": "react-on-rails-pro",  
  "version": "16.1.1",
  "license": "UNLICENSED",
  "dependencies": {
    "react-on-rails": "*"
  },
  "exports": {
    ".": {
      "react-server": "./lib/ReactOnRailsRSC.js",
      "node": "./lib/ReactOnRails.node.js",
      "default": "./lib/ReactOnRails.full.js"
    }
    // ... RSC and Pro-specific exports
  }
}

✅ Conditional exports for RSC support
✅ Dependency on core package using workspace protocol


🎨 Code Quality

Strengths:

  • Clean separation of concerns between core and pro
  • Excellent use of TypeScript for type safety
  • Proper error messages guide users to correct package
  • DRY principle maintained (Pro reuses core rendering logic)
  • Registry architecture is elegant and extensible
  • Good use of globalThis.ReactOnRails for shared state

Pattern Highlights:

// Core: Simple synchronous registry
const registeredComponents = new Map<string, RegisteredComponent>();

// Pro: Advanced async registry  
const componentRegistry = new CallbackRegistry<RegisteredComponent>('component');

This dual approach maintains simplicity for core users while providing advanced features for Pro users.


🔐 Security & Licensing

  • ✅ No Pro code leaked into MIT-licensed directories
  • ✅ Pro package properly marked as UNLICENSED
  • ✅ All Pro files have copyright headers
  • ✅ LICENSE.md accurately reflects new structure
  • ✅ No license validation mechanism bypasses detected

📝 Recommendations

  1. Fix LinkedIn link check (optional, can be done separately):

    • Either update .github/markdown-link-check-config.json to ignore LinkedIn URLs
    • Or replace the LinkedIn link in docs/testimonials/hvmn.md with an alternative
  2. Consider adding (future enhancement):

    • A migration guide in the Pro package README for users upgrading from mixed setup
    • Automated detection if users accidentally mix core and pro imports

✨ Conclusion

This PR represents excellent engineering work. The separation is:

  • ✅ Architecturally sound
  • ✅ Properly licensed
  • ✅ Well-tested
  • ✅ Fully documented
  • ✅ Backward compatible

The only CI failure is a false positive (LinkedIn anti-bot protection). All functional tests pass.

Recommendation: APPROVE and merge 🚀

The code is production-ready, and the architectural separation will benefit both core and pro users by providing clearer boundaries and better maintainability.


Reviewed: All key files including package structure, registries, base objects, entry points, LICENSE.md, workspace configuration, and CI results

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