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

  • Documentation
    • Added a comprehensive Pro-package separation plan with migration checkpoints, testing, licensing, and rollback guidance.
  • New Features
    • Introduced a standalone Pro package with its own public entrypoints, registries, server-streaming and client-rendering support, utilities, and build/test configuration.
  • Behavior Changes
    • Core package defers client rendering to full page load and surfaces upgrade notices for Pro-only capabilities.
  • Tests
    • Added and updated tests covering client rendering, registries, server rendering, and Pro package behavior.
  • Packaging & CI
    • Updated workspaces, build/publish scripts, gitignore, and CI to include and build the Pro package.

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>
Copy link
Contributor

coderabbitai bot commented Sep 29, 2025

Walkthrough

Introduces a new packages/react-on-rails-pro package and moves Pro / React Server Components functionality into it; adds core client renderer and default-export registries to core; redirects many imports/exports to package-scoped paths; updates workspaces, TypeScript configs, tests, CI, and license boundaries.

Changes

Cohort / File(s) Summary
Docs
docs/JS_PRO_PACKAGE_SEPARATION_PLAN.md
New migration and architecture plan describing package split, milestones, validation, licensing, rollback, and implementation steps.
New Pro package (manifest & config)
packages/react-on-rails-pro/package.json, packages/react-on-rails-pro/tsconfig.json, packages/react-on-rails-pro/jest.config.js
Add pro package metadata, exports map, scripts, TS config, and package-scoped Jest config.
Core client renderer & startup
packages/react-on-rails/src/ClientRenderer.ts, packages/react-on-rails/src/clientStartup.ts, packages/react-on-rails/src/ReactOnRails.client.ts
Add ClientRenderer (renderComponent, renderAllComponents, reactOnRailsComponentLoaded), change startup to page-load-driven render; simplify client factory export.
Core registries
packages/react-on-rails/src/ComponentRegistry.ts, packages/react-on-rails/src/StoreRegistry.ts
Add default-exported in-memory ComponentRegistry and StoreRegistry APIs (register/get/getStore/setStore/etc.) with Pro-only getOrWaitFor* placeholders that throw when Pro absent.
Server-side gating & lookup
packages/react-on-rails/src/ReactOnRails.node.ts, packages/react-on-rails/src/serverRenderReactComponent.ts
Replace pro streaming export with runtime stub that errors without Pro; serverRenderReactComponent now resolves components via globalThis.ReactOnRails.getComponent(...).
Pro entrypoint & integration
packages/react-on-rails-pro/src/index.ts, packages/react-on-rails-pro/src/createReactOnRailsPro.ts, packages/react-on-rails-pro/src/ReactOnRails.*.ts
New Pro entrypoints (client/full/node), createReactOnRailsPro factory wiring Pro registries, pro-only APIs, and immediate-hydration startup behavior.
Pro modules — import retargeting
packages/react-on-rails-pro/src/* (e.g., registerServerComponent/*, streamServerRenderedReactComponent.ts, wrapServerComponentRenderer/*, ReactOnRailsRSC.ts, RSCRequestTracker.ts)
Replace many relative/private imports with package-scoped react-on-rails/* or sibling pro paths; mostly import-resolution changes.
Pro utilities & helpers
packages/react-on-rails-pro/src/utils.ts, packages/react-on-rails-pro/src/loadJsonFile.ts, packages/react-on-rails-pro/src/RSCRoute.tsx
New utility helpers (fetch wrapper, createRSCPayloadKey, wrapInNewPromise, extractErrorMessage), cached JSON loader, and small typing note in RSCRoute.
Base object & factories
packages/react-on-rails/src/base/client.ts, packages/react-on-rails/src/base/full.ts, packages/react-on-rails/src/createReactOnRails.ts
New base factories for client/full objects and createReactOnRails function that composes core object, wires globalThis.ReactOnRails, and provides pro-only stubs.
Tests — add/adjust
packages/react-on-rails/tests/*, packages/react-on-rails-pro/tests/*, packages/react-on-rails-pro/tests/jest.setup.js
Add ClientRenderer tests; update tests to default-import registries; retarget pro tests to new paths; add Pro Jest setup polyfills/mocks for streams/fetch bodies.
Packaging, workspaces & TS
package.json, packages/react-on-rails/package.json, tsconfig.json, packages/react-on-rails/tsconfig.json, .gitignore, CHANGELOG.md, LICENSE.md
Add pro workspace, change root build script to explicit workspaces, update core package exports (remove pro entries), enable TS declarations/maps, update gitignore and license boundaries, update changelog wording.
Removed/relocated Pro NOTICE & dummy apps
packages/react-on-rails/src/pro/NOTICE, react_on_rails_pro/spec/dummy/**, spec/dummy/**
Delete legacy Pro NOTICE; update dummy apps and specs to import react-on-rails-pro; add initializer config changes.
Misc scripts & CI
.github/workflows/package-js-tests.yml, script/convert
Add CI build step for renderer package; adjust script/convert replacements to target pro package.json.
ESLint / knip / tooling
eslint.config.ts, knip.ts
Adjust parserOptions/projectService, add pro package ESLint target, broaden file globs; update knip workspace entries and ignored paths for new package layout.

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant CoreClient as ClientRenderer (core)
  participant ComponentReg as ComponentRegistry (core)
  participant StoreReg as StoreRegistry (core)
  participant ProLayer as react-on-rails-pro (optional)
  participant React

  Note over Browser,CoreClient: Core-only flow
  Browser->>CoreClient: onPageLoaded -> renderAllComponents()
  CoreClient->>StoreReg: initialize stores (hydrate if present)
  loop components (.js-react-on-rails-component)
    CoreClient->>ComponentReg: get(name)
    alt renderer registered
      ComponentReg-->>CoreClient: renderFunction
      CoreClient->>React: delegateToRenderer(renderFunction)
    else normal component
      ComponentReg-->>CoreClient: component
      CoreClient->>React: createReactOutput -> reactHydrateOrRender
    end
    React-->>Browser: mount/hydrate DOM
  end

  Note over Browser,ProLayer: When Pro is installed
  Browser->>ProLayer: import react-on-rails-pro
  ProLayer->>ComponentReg: register components (pro registries)
  ProLayer->>StoreReg: register storeGenerators + immediate hydration
  ProLayer->>CoreClient: override/augment lifecycle (componentLoaded/storeLoaded)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • justin808
  • alexeyr-ci2
  • ihabadham
  • Judahmeek

Poem

Hop hop — I split Pro with care,
Tiny packages float on air.
Registries hum and renderers spring,
Core stays light — Pro adds the bling.
Rabbity cheers for modular flair! 🐇

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly and accurately reflects the primary change—splitting the JavaScript Pro code into its own package—and avoids vague language, making it clear for anyone scanning the PR history what the core update entails.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 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

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

❤️ Share

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

Copy link

claude bot commented Sep 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

AbanoubGhadban and others added 3 commits September 29, 2025 20:31
✅ 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>
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 107
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.


try {
const componentObj = ComponentRegistry.get(componentName);
const componentObj = globalThis.ReactOnRails.getComponent(componentName);
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

Critical: globalThis.ReactOnRails is undefined in server context.

The pipeline failure indicates that globalThis.ReactOnRails is not available when this code executes server-side. This change from ComponentRegistry.get(componentName) to globalThis.ReactOnRails.getComponent(componentName) will cause runtime crashes.

Verify how globalThis.ReactOnRails is initialized in server bundles:

#!/bin/bash
# Search for globalThis.ReactOnRails initialization patterns in server-side code
rg -n "globalThis\.ReactOnRails\s*=" --type=ts -g '!*.test.ts'

# Find server-side entry points that should initialize ReactOnRails
rg -n "ReactOnRails\.full" --type=ts -g '!*.test.ts'

# Check if ComponentRegistry is still available as a fallback
ast-grep --pattern 'import $_ from $$$ComponentRegistry$$$'

Consider reverting to direct ComponentRegistry.get(componentName) access or ensuring globalThis.ReactOnRails is properly initialized before this code path executes.

🧰 Tools
🪛 GitHub Actions: JS unit tests for Renderer package

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

🤖 Prompt for AI Agents
packages/react-on-rails/src/serverRenderReactComponent.ts around line 150: the
new call to globalThis.ReactOnRails.getComponent(componentName) will crash in
server builds because globalThis.ReactOnRails is not initialized there; replace
this usage with a safe fallback — call ComponentRegistry.get(componentName)
(ensure ComponentRegistry is imported) or guard access with if
(globalThis?.ReactOnRails?.getComponent) { ... } else {
ComponentRegistry.get(componentName) } so server-side rendering uses the
registry directly, or ensure any server entry point initializes
globalThis.ReactOnRails before this line executes.

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.

AbanoubGhadban and others added 6 commits September 30, 2025 11:30
✅ Checkpoint 4.1: Move Pro JavaScript/TypeScript files
- Moved all 22 files from packages/react-on-rails/src/pro/ to packages/react-on-rails-pro/src/
- Used git mv to preserve complete git history for all files
- Preserved directory structure including registerServerComponent/ and wrapServerComponentRenderer/
- Git shows files as "renamed" maintaining full commit history

Files moved:
- CallbackRegistry.ts
- ClientSideRenderer.ts
- ComponentRegistry.ts
- StoreRegistry.ts
- ReactOnRailsRSC.ts
- PostSSRHookTracker.ts
- RSCProvider.tsx, RSCRequestTracker.ts, RSCRoute.tsx
- ServerComponentFetchError.ts
- getReactServerComponent.client.ts, getReactServerComponent.server.ts
- injectRSCPayload.ts
- streamServerRenderedReactComponent.ts
- transformRSCNodeStream.ts, transformRSCStreamAndReplayConsoleLogs.ts
- registerServerComponent/ (3 files: client.tsx, server.tsx, server.rsc.ts)
- wrapServerComponentRenderer/ (3 files: client.tsx, server.tsx, server.rsc.tsx)

✅ Checkpoint 4.2: Update import paths in moved files
- Updated 56 import statements from relative '../' paths to 'react-on-rails' package imports
- Fixed all imports from core package (types, utils, context, etc.)
- Preserved relative imports within pro package (./CallbackRegistry.ts, etc.)
- No circular dependencies introduced

✅ Checkpoint 4.3: Remove pro directory from core
- Deleted empty packages/react-on-rails/src/pro/ directory
- Removed NOTICE file from old location
- Verified no references to old pro paths remain

The pro package is now completely separated from the core package with full
git history preserved for all moved files. All imports correctly reference
the react-on-rails package as a dependency.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Updated all import paths in moved pro package files to correctly reference
the core react-on-rails package:

- Updated 56 import statements from relative '../' paths to 'react-on-rails' imports
- Common patterns updated:
  - '../types/index.ts' → 'react-on-rails/types'
  - '../context.ts' → 'react-on-rails/context'
  - '../createReactOutput.ts' → 'react-on-rails/createReactOutput'
  - '../utils.ts' → 'react-on-rails/utils'
  - And many other core package utilities

- Preserved relative imports within pro package (./CallbackRegistry.ts, etc.)
- No circular dependencies introduced

Files updated:
- CallbackRegistry.ts, ClientSideRenderer.ts, ComponentRegistry.ts, StoreRegistry.ts
- RSCProvider.tsx, RSCRequestTracker.ts, ReactOnRailsRSC.ts
- getReactServerComponent.client.ts, getReactServerComponent.server.ts
- injectRSCPayload.ts, streamServerRenderedReactComponent.ts
- transformRSCStreamAndReplayConsoleLogs.ts
- registerServerComponent/ (all files)
- wrapServerComponentRenderer/ (all files)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Updated clientStartup.ts to work without pro features:
- Removed imports from pro/ClientSideRenderer.ts
- Simplified to on-demand component rendering (no automatic hydration)
- Removed pro features: immediate hydration, unmountAll, automatic store hydration
- Core package now builds successfully

Core behavior:
- Components render on-demand via reactOnRailsComponentLoaded()
- Pro package will provide automatic hydration and lifecycle management

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Corrected misunderstanding of core vs pro behavior:

**Core Package (MIT):**
- Waits for FULL page load before rendering
- Then renders/hydrates ALL components at once
- Simple, synchronous approach
- Added renderAllComponents() and renderAllStores() functions

**Pro Package (Commercial):**
- Can hydrate BEFORE page load (immediate_hydration: true)
- Can hydrate on-demand with async component loading
- More advanced lifecycle management with unmounting

Updated ClientRenderer.ts:
- Added renderAllComponents() to render all components after page load
- Added renderAllStores() to initialize all stores
- Kept reactOnRailsComponentLoaded() for individual component rendering

Updated clientStartup.ts:
- Now calls renderAllComponents() when page is fully loaded
- Correct comments explaining core vs pro behavior

This matches the original React on Rails behavior where core package
renders all components after page load, and pro package adds advanced
features like immediate hydration and on-demand rendering.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
✅ Checkpoint 5.1: Identify pro-related tests
- Identified 4 test files that test pro-only functionality
- All tests importing from pro/ directories

✅ Checkpoint 5.2: Move pro tests
- Moved using git mv to preserve history:
  - streamServerRenderedReactComponent.test.jsx (streaming is pro-only)
  - registerServerComponent.client.test.jsx (RSC is pro-only)
  - injectRSCPayload.test.ts (RSC payload injection is pro-only)
  - SuspenseHydration.test.tsx (RSC hydration behavior is pro-only)

✅ Checkpoint 5.3: Update remaining core tests
- Updated serverRenderReactComponent.test.ts to use core ComponentRegistry
- Core ComponentRegistry and StoreRegistry tests already properly test core functionality
- Tests for pro method stubs already exist (throw errors directing to upgrade)

Pro tests now live in packages/react-on-rails-pro/tests/ with full git
history preserved. Core tests only test core functionality.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
✅ Checkpoint 6.1: Create pro package main entry point

Created packages/react-on-rails-pro/src/index.ts that:
- Re-exports everything from core react-on-rails package
- Imports and enhances ReactOnRailsCore with pro features
- Replaces core registries with pro registries (ComponentRegistry, StoreRegistry)
- Adds pro rendering methods (renderOrHydrateComponent, hydrateStore, unmountAll)
- Implements pro client startup with immediate hydration support

Key Pro Features:
- Async registry methods (getOrWaitForComponent, getOrWaitForStore)
- Immediate hydration before page load (immediate_hydration: true)
- Component unmounting and lifecycle management
- Enhanced rendering with on-demand async loading

Pro Startup Behavior:
- Hydrates immediate_hydration components BEFORE page load
- Hydrates remaining components AFTER page load
- Properly unmounts components on page unload

This entry point replaces globalThis.ReactOnRails with the enhanced pro version,
providing all core functionality plus advanced pro features.

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

AbanoubGhadban and others added 11 commits September 30, 2025 11:57
✅ Fixed tsconfig.json to use standard package imports
✅ Updated core package exports to expose all necessary modules
✅ Removed pro-specific exports from core package.json
✅ Fixed React.use type error in RSCRoute.tsx
✅ Pro package builds successfully using react-on-rails as dependency
Added .gitignore patterns to exclude TypeScript build artifacts that
accidentally end up in src directories:
- *.js, *.d.ts, *.d.cts, *.cjs, *.map files in packages/*/src/
- Excluded test files from ignore (*.test.js, *.spec.js)

Removed accidentally committed build artifacts from packages/react-on-rails/src/
These files should only exist in the lib/ directory.

The lib/ directory is already properly ignored by /packages/*/lib pattern.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add react-on-rails-pro to workspace packages
- Configure sequential build to ensure core builds before pro
- Test confirmed both packages build successfully

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Update license scope to reflect Pro code now in separate
packages/react-on-rails-pro/ package instead of embedded
in react-on-rails/src/pro/

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Update CHANGELOG.md to reflect pro code now in separate package
- Mark Steps 7-8 as complete in implementation plan
- Document workspace configuration and license updates

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Setup globalThis.ReactOnRails in test beforeEach to provide
getComponent method that serverRenderReactComponent now uses

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
All 10 steps of the JS Pro package separation have been
successfully completed:
- Steps 1-6: Package structure and implementation
- Step 7: Workspace configuration
- Step 8: License compliance
- Step 9: Comprehensive testing
- Step 10: Documentation and cleanup

All success criteria met ✓

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
TypeScript's incremental compilation was causing stale
tsbuildinfo cache, preventing files from being emitted
on subsequent builds. Setting "incremental": false
ensures reliable builds.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…ncremental

Instead of disabling incremental compilation, configure TypeScript
to place tsconfig.tsbuildinfo inside lib/ directory. This way the
clean script (rm -rf ./lib) removes it, preventing stale cache.

Benefits over previous fix:
- Keeps incremental compilation for faster rebuilds
- Matches core package behavior
- Still reliable (tsbuildinfo cleaned on every build)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Added declaration and declarationMap options in tsconfig.json for better type definitions.
- Updated build scripts in package.json files to remove unnecessary --declaration flag.
- Adjusted file patterns in package.json to include both .js and .d.ts files.
- Introduced a new tsconfig.json for the react-on-rails package to manage its own build settings.

These changes improve type declaration handling and streamline the build process across packages.
- Removed unnecessary "exclude" field from tsconfig.json in both react-on-rails and react-on-rails-pro packages.
- Simplified TypeScript configuration to focus on source files.

These changes streamline the TypeScript setup for better clarity and maintainability.
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 30
// 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.

- Updated import paths in multiple files to reference the new local utils.ts module instead of the previous utils from 'react-on-rails'.
- Introduced utils.ts with utility functions including fetch, createRSCPayloadKey, wrapInNewPromise, and extractErrorMessage.

These changes enhance modularity and maintainability of the codebase.
- Updated import paths for loadJsonFile in ReactOnRailsRSC and getReactServerComponent to use the new local loadJsonFile.ts module.
- Removed the old loadJsonFile reference from package.json in react-on-rails.
- Introduced a new loadJsonFile.ts file in react-on-rails-pro with functionality to load and cache JSON files.

These changes improve modularity and ensure the correct loading of JSON files in the new package structure.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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.

import * as React from 'react';
import { createFromReadableStream } from 'react-on-rails-rsc/client.browser';
import { createRSCPayloadKey, fetch, wrapInNewPromise, extractErrorMessage } from '../utils.ts';
import { createRSCPayloadKey, fetch, wrapInNewPromise, extractErrorMessage } from './utils.ts';
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

🧩 Analysis chain

Verify the utils import path change.

The import path changed from ../utils.ts to ./utils.ts, suggesting the file structure was reorganized. Please ensure that utils.ts exists at packages/react-on-rails-pro/src/utils.ts.

Run the following script to verify the utils file location:


🏁 Script executed:

#!/bin/bash
# Verify that utils.ts exists in the expected location
fd -t f "^utils\.ts$" packages/react-on-rails-pro/src/ --max-depth 1

Length of output: 40


Reorder imports to satisfy ESLint import-order rule. Move the package import (import { RailsContext } from 'react-on-rails/types';) above the relative import (import { createRSCPayloadKey, fetch, wrapInNewPromise, extractErrorMessage } from './utils.ts';) in packages/react-on-rails-pro/src/getReactServerComponent.client.ts.

🤖 Prompt for AI Agents
In packages/react-on-rails-pro/src/getReactServerComponent.client.ts around line
17, the import order violates the ESLint import-order rule: the package import
for RailsContext should appear before the relative utils import; reorder the
imports so that "import { RailsContext } from 'react-on-rails/types';" is placed
above "import { createRSCPayloadKey, fetch, wrapInNewPromise,
extractErrorMessage } from './utils.ts';" and save to satisfy the lint rule.

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.

…-pro

- Changed the version of "react-on-rails" in package.json from "^16.1.0" to "16.1.0" for consistency.
- Enhanced jest.config.js to allow Jest to transform the react-on-rails package from node_modules.
- Updated import paths in test files to reflect the new structure, ensuring proper module resolution.
- Added TypeScript error suppression for async components in SuspenseHydration.test.tsx.

These changes enhance the testing setup and ensure compatibility with the updated package structure.
- Changed the version of "react-on-rails" in package.json from "16.1.0" to "*" to allow for more flexible versioning.
- Removed the specific "react-on-rails" entry from yarn.lock to reflect the updated dependency.
- Updated import path in client.tsx to use the local index.ts instead of the external package.

These changes enhance compatibility and maintainability of the react-on-rails-pro package.
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.

Copy link

claude bot commented Oct 2, 2025

test

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

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

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

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

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

- Modified the client startup process to use a setTimeout for improved execution timing.
- Enabled auto-loading of bundles in the React on Rails initializer and added an option for immediate hydration.

These changes enhance the initialization process and improve the overall performance of the application.
- Changed the target package.json path from "react-on-rails" to "react-on-rails-pro" in the convert script.
- Updated the version of "react" and "react-dom" to "18.0.0" in the dummy package.json files.

These changes ensure the convert script correctly references the new package structure and versions.
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

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

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. 🚀

- Introduced a new step to build the Renderer package before running JS unit tests in the GitHub Actions workflow.
- This change ensures that the latest build is tested, improving the reliability of the CI process.
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 +50
"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 16 to 37
export * from 'react-on-rails';

// Import core ReactOnRails to enhance it
import ReactOnRailsCore from 'react-on-rails/ReactOnRails.client';

// Import pro registries and features
import type {
Store,
StoreGenerator,
RegisteredComponent,
ReactComponentOrRenderFunction,
} from 'react-on-rails/types';
import * as ProComponentRegistry from './ComponentRegistry.ts';
import * as ProStoreRegistry from './StoreRegistry.ts';
import {
renderOrHydrateComponent,
hydrateStore,
renderOrHydrateAllComponents,
hydrateAllStores,
renderOrHydrateImmediateHydratedComponents,
hydrateImmediateHydratedStores,
unmountAll,
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 module resolution for core imports.

The JS unit test job fails because TypeScript cannot resolve 'react-on-rails', 'react-on-rails/ReactOnRails.client', and 'react-on-rails/pageLifecycle'. Please expose those subpaths from the core package (e.g., via packages/react-on-rails/package.json exports/typesVersions) or add explicit paths mappings in the Pro tsconfig so these imports resolve during compilation. Without that, the Pro entrypoint will not build. (Reference: pipeline “JS unit tests for Renderer package” failure.)

🧰 Tools
🪛 GitHub Actions: JS unit tests for Renderer package

[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.

🤖 Prompt for AI Agents
In packages/react-on-rails-pro/src/index.ts around lines 16 to 37, TypeScript
cannot resolve imports of 'react-on-rails',
'react-on-rails/ReactOnRails.client', and 'react-on-rails/pageLifecycle'; fix by
updating module resolution so these subpaths are visible to the Pro package:
either add the missing exports and typings in
packages/react-on-rails/package.json (add "exports" entries for ".",
"./ReactOnRails.client", and "./pageLifecycle" and corresponding "types" or
update "typesVersions") or add explicit "paths" mappings in
packages/react-on-rails-pro/tsconfig.json pointing those module specifiers to
the core package source/type files; after making the change, run tsc in the Pro
package to confirm imports resolve.

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.

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! 🚀

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

- Simplified the expectation for the JavaScript pack tag in the ReactOnRailsHelper spec by removing conditional checks based on the CI_PACKER_VERSION environment variable.
- This change enhances the clarity of the test and ensures consistent behavior across different environments.
- Added functionality to track rendered React roots for proper cleanup on page unload, preventing memory leaks.
- Implemented unmounting logic for both React 16-17 and 18+ using the appropriate APIs.
- Updated client startup behavior to ensure it only runs in the browser environment, improving execution timing and reliability.

These changes enhance the overall performance and memory management of the application.
…defer loading

- Modified the expectation for the JavaScript pack tag in the ReactOnRailsHelper spec to set the `defer` option to true, ensuring scripts are loaded in a non-blocking manner.
- This change improves the performance of the component rendering process in tests.
- Changed the `reactOnRailsPageLoaded` function to return a resolved promise after invoking the startup logic, improving consistency in handling asynchronous operations.
- Updated the `reactOnRailsStoreLoaded` function to suppress unused variable warnings, enhancing code clarity.
- Added an ESLint directive to the `unmountAllComponents` function to address the use of deprecated APIs, ensuring better compliance with TypeScript standards.

These changes streamline the client-side rendering process and improve code quality.
…s packages

- Added specific ESLint configurations for the react-on-rails and react-on-rails-pro packages to handle import resolution issues in a monorepo setup.
- Introduced new tsconfig.eslint.json files for both packages to extend the base TypeScript configuration and include relevant source and test files.
- Updated the ESLint config to disable certain rules for the react-on-rails-pro package, ensuring compatibility with TypeScript's validation of imports.
- Made minor adjustments to the RSCRoute component to remove unnecessary TypeScript directives, improving code clarity.

These changes improve linting accuracy and TypeScript support across the project.
- Deleted tsconfig.eslint.json files from both react-on-rails and react-on-rails-pro packages as they are no longer needed.
- This cleanup simplifies the project structure and eliminates redundancy in TypeScript configuration.
- Updated the knip configuration for the react-on-rails and react-on-rails-pro packages, consolidating entry points and improving project structure.
- Removed the obsolete loadJsonFile function from the ClientRenderer, streamlining the codebase and eliminating unnecessary complexity.
- Adjusted ignore patterns to ensure proper handling of test utilities and build outputs.

These changes enhance the organization and maintainability of the project.
- Consolidated entry points in the knip configuration for both react-on-rails and react-on-rails-pro packages, improving project organization.
- Introduced new base client and full objects to streamline the creation of ReactOnRails instances, enhancing code maintainability.
- Added new methods for server-side rendering and component hydration in the Pro package, expanding functionality.
- Updated package.json files to reflect new exports and improve module resolution.

These changes enhance the overall structure and capabilities of the React on Rails framework.
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.

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.

};
}

export type BaseClientObjectType = ReturnType<typeof createBaseClientObject>;
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

Remove unused exported type to satisfy lint.

BaseClientObjectType isn’t used, triggering the same lint failure as in the CI logs. Please remove or reference it; dropping the export fixes the pipeline:

-export type BaseClientObjectType = ReturnType<typeof createBaseClientObject>;
📝 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
export type BaseClientObjectType = ReturnType<typeof createBaseClientObject>;
// (line removed)
🧰 Tools
🪛 GitHub Actions: Lint JS and Ruby

[warning] 201-201: Unused exported type: BaseClientObjectType - client.ts:201:13

🤖 Prompt for AI Agents
In packages/react-on-rails/src/base/client.ts around line 201, the exported type
alias BaseClientObjectType = ReturnType<typeof createBaseClientObject> is unused
and causes a lint failure; remove the export (or the entire type) so the unused
exported identifier is gone, or alternatively reference it where needed; update
any imports that relied on it if removed.

};
}

export type BaseFullObjectType = ReturnType<typeof createBaseFullObject>;
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

Resolve lint failure from unused export.

BaseFullObjectType isn’t referenced anywhere, and the lint job is failing on this warning. Either consume the type or drop the export; removing it is the quickest unblock:

-export type BaseFullObjectType = ReturnType<typeof createBaseFullObject>;
📝 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
export type BaseFullObjectType = ReturnType<typeof createBaseFullObject>;
🧰 Tools
🪛 GitHub Actions: Lint JS and Ruby

[warning] 32-32: Unused exported type: BaseFullObjectType - full.ts:32:13

🤖 Prompt for AI Agents
In packages/react-on-rails/src/base/full.ts around line 32, the exported type
alias BaseFullObjectType is unused and causing a lint failure; remove the export
(either delete the line or change it to a non-exported type alias) so the
unused-export warning is resolved, or if the type will be needed later,
reference it where appropriate instead of exporting it unused.

Comment on lines +17 to +23
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>) : {};
const storeGenerator = StoreRegistry.getStoreGenerator(name);
const store = storeGenerator(props, railsContext);
StoreRegistry.setStore(name, store);
}
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

Add defensive JSON parsing for stores.

A malformed store payload will throw directly from JSON.parse, tearing down initialization with no context. Wrap the parse in try/catch and surface which store failed so users can diagnose quickly.

🤖 Prompt for AI Agents
In packages/react-on-rails/src/ClientRenderer.ts around lines 17 to 23, the call
to JSON.parse(el.textContent) can throw on malformed JSON and break
initialization; wrap the parse in a try/catch that captures parsing errors,
include the store name and the raw payload in the logged error (e.g.,
console.error or the existing logger) so users can see which store failed, and
set props to an empty object on parse failure so initialization continues safely
(or rethrow with a clear message if you prefer to fail fast).

Comment on lines +32 to +38
reactOnRailsPageLoaded(): Promise<void> {
reactOnRailsPageLoaded();
return Promise.resolve();
},

reactOnRailsComponentLoaded(domId: string): Promise<void> {
return reactOnRailsComponentLoaded(domId);
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 recursion in page/component loaded overrides.

Line 32 and Line 37 end up calling the method being defined, not the helpers from clientStartup.ts / ClientRenderer.ts, so the first invocation recurses until the stack blows up and the client never boots. Alias the imports and invoke those instead, e.g.:

-import { clientStartup, reactOnRailsPageLoaded } from './clientStartup.ts';
-import { reactOnRailsComponentLoaded } from './ClientRenderer.ts';
+import {
+  clientStartup,
+  reactOnRailsPageLoaded as coreReactOnRailsPageLoaded,
+} from './clientStartup.ts';
+import { reactOnRailsComponentLoaded as coreReactOnRailsComponentLoaded } from './ClientRenderer.ts';-    reactOnRailsPageLoaded(): Promise<void> {
-      reactOnRailsPageLoaded();
-      return Promise.resolve();
-    },
+    reactOnRailsPageLoaded(): Promise<void> {
+      return Promise.resolve(coreReactOnRailsPageLoaded());
+    },-    reactOnRailsComponentLoaded(domId: string): Promise<void> {
-      return reactOnRailsComponentLoaded(domId);
-    },
+    reactOnRailsComponentLoaded(domId: string): Promise<void> {
+      return coreReactOnRailsComponentLoaded(domId);
+    },
📝 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
reactOnRailsPageLoaded(): Promise<void> {
reactOnRailsPageLoaded();
return Promise.resolve();
},
reactOnRailsComponentLoaded(domId: string): Promise<void> {
return reactOnRailsComponentLoaded(domId);
// packages/react-on-rails/src/createReactOnRails.ts
import {
clientStartup,
reactOnRailsPageLoaded as coreReactOnRailsPageLoaded,
} from './clientStartup.ts';
import { reactOnRailsComponentLoaded as coreReactOnRailsComponentLoaded } from './ClientRenderer.ts';
const createReactOnRails = {
// …other methods and properties…
reactOnRailsPageLoaded(): Promise<void> {
return Promise.resolve(coreReactOnRailsPageLoaded());
},
reactOnRailsComponentLoaded(domId: string): Promise<void> {
return coreReactOnRailsComponentLoaded(domId);
},
// …other methods and properties…
};
export default createReactOnRails;
🤖 Prompt for AI Agents
In packages/react-on-rails/src/createReactOnRails.ts around lines 32 to 38, the
methods reactOnRailsPageLoaded and reactOnRailsComponentLoaded are calling
themselves (causing recursion) instead of the helper functions from
clientStartup.ts / ClientRenderer.ts; update the import statements to alias the
helper functions (for example reactOnRailsPageLoadedClient and
reactOnRailsComponentLoadedClient) and invoke those aliased helpers inside the
method bodies so the methods delegate to the client helpers rather than
recursively calling themselves.

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.

1 participant