Skip to content

Remove immediate_hydration feature from codebase#2834

Merged
justin808 merged 14 commits intomainfrom
jg/2142-rm-immediate-hydration
Apr 7, 2026
Merged

Remove immediate_hydration feature from codebase#2834
justin808 merged 14 commits intomainfrom
jg/2142-rm-immediate-hydration

Conversation

@justin808
Copy link
Copy Markdown
Member

@justin808 justin808 commented Mar 24, 2026

Summary

  • Removes the immediate_hydration configuration option, helper parameter, data-immediate-hydration HTML attribute, and all related normalization/validation logic
  • Immediate hydration is now always enabled for Pro users and disabled for non-Pro users — no per-component toggle
  • The config.immediate_hydration deprecated setter/getter (added in 16.2.0) is fully removed
  • Pro stream_react_component no longer injects immediate_hydration: true; Pro inline hydration scripts are now unconditionally added based on react_on_rails_pro?
  • JS Pro package: renamed renderOrHydrateImmediateHydratedComponentsrenderOrHydrateCompleteComponents (now selects all components, not just those with the removed attribute)

Closes #2142

Test plan

  • RuboCop: 0 offenses across all changed files (173 inspected)
  • RBS validation passed
  • TypeScript type-check passed (all 4 packages)
  • Gem unit tests: 160 examples, 0 failures
  • CI: dummy app integration tests (helper spec, E2E)
  • CI: Pro integration tests

🤖 Generated with Claude Code


Note

Medium Risk
Medium risk because it removes a previously supported configuration/keyword option and changes client hydration triggering logic for streaming/non-streaming pages, which could affect render timing or cause missed hydration if edge cases exist.

Overview
Removes the immediate_hydration feature end-to-end: drops config.immediate_hydration, per-helper keyword args, and the data-immediate-hydration DOM attribute, with Pro now hydrating early automatically and non-Pro hydrating on page load (no per-component override).

Updates Pro streaming hydration to hydrate any complete streamed component/store (nextSibling-present) rather than filtering by the removed attribute, and avoids caching renderers created before railsContext exists; stream_react_component now warns if immediate_hydration: is provided.

Hardens/cleans up behavior and tests: adds one-time warnings when removed options are passed, tightens keyword-arg validation for redux_store, fixes HTML attribute escaping for store names to prevent attribute injection, and updates/extends Ruby, TS, and dummy app tests/docs accordingly.

Reviewed by Cursor Bugbot for commit 389ac3a. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • Chores
    • Removed the immediate_hydration option and per-component HTML attribute; immediate hydration is now Pro‑gated (enabled for Pro, disabled otherwise).
  • Bug Fixes / Behavior
    • Passing immediate_hydration to streaming calls now emits a warning; store name serialization escaping fixed to prevent attribute injection.
  • Documentation
    • Changelog and inline docs updated to reflect the new Pro‑gated hydration behavior.
  • Tests
    • Tests updated to reflect removed option and to verify store-name escaping.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR removes the immediate_hydration feature across the codebase: config accessors, helper parameters, HTML attributes, normalization/warning helpers, tests, and client entrypoints were removed or renamed so hydration is determined solely by Pro license status.

Changes

Cohort / File(s) Summary
Changelog
CHANGELOG.md
Documented removal of the immediate_hydration feature and guidance to remove related references.
Client (Pro) renderer & startup
packages/react-on-rails-pro/src/ClientSideRenderer.ts, packages/react-on-rails-pro/src/createReactOnRailsPro.ts, packages/react-on-rails/src/clientStartup.ts
Removed detection of data-immediate-hydration, renamed exports from *ImmediateHydrated**Complete*, updated startup to call the new entrypoints and gate invocation on railsContext?.rorPro.
Ruby configuration & RBS
react_on_rails/lib/react_on_rails/configuration.rb, react_on_rails/sig/react_on_rails/configuration.rbs
Removed immediate_hydration accessor and initializer parameter plus related deprecation state and type declarations.
Controller & helper signatures
react_on_rails/lib/react_on_rails/controller.rb, react_on_rails/lib/react_on_rails/helper.rb, react_on_rails/sig/react_on_rails/controller.rbs
Removed immediate_hydration: keyword from redux_store signatures and eliminated normalization/propagation of that flag.
Render options & utils
react_on_rails/lib/react_on_rails/react_component/render_options.rb, react_on_rails/lib/react_on_rails/utils.rb
Deleted RenderOptions#immediate_hydration, removed normalize_immediate_hydration and Pro-install warning helper and their logic.
Pro helper & generated scripts
react_on_rails/lib/react_on_rails/pro_helper.rb, react_on_rails_pro/app/helpers/react_on_rails_pro_helper.rb
Removed data-immediate-hydration attributes from generated JSON/script tags; inline hydration decision now uses ReactOnRails::Utils.react_on_rails_pro?; stream_react_component warns and strips unsupported :immediate_hydration.
Doctor / analysis
react_on_rails/lib/react_on_rails/doctor.rb
Stopped detecting/reporting config.immediate_hydration in performance analysis; adjusted RuboCop directives.
Tests — removed/updated
react_on_rails/spec/..., react_on_rails_pro/spec/...
Removed or updated specs covering immediate_hydration behavior, removed expectations for data-immediate-hydration, added streaming helper warning spec, adjusted CSP/nonce assertions and escaped store-name test.
E2E & test utilities
react_on_rails/spec/dummy/e2e/playwright/.../basic_components.spec.js, react_on_rails/spec/dummy/spec/support/selenium_logger.rb
Stopped excluding Pro-license immediate_hydration console errors from failure filters so such messages are now captured as test failures.
Spec dummy app views
react_on_rails/spec/dummy/app/views/..., react_on_rails_pro/spec/dummy/app/views/layouts/application.html.erb
Removed immediate_hydration: true call-sites and updated ERB comments to reflect Pro-driven immediate hydration.
App generator types
packages/create-react-on-rails-app/src/create-app.ts
Narrowed proModeLabel type from `string

Sequence Diagram(s)

sequenceDiagram
  participant Server as Rails Server
  participant Client as Browser (ClientStartup)
  participant Renderer as ClientSideRenderer
  participant Pro as ReactOnRails Pro (rorPro)

  Server->>Client: Render HTML + <script class="js-react-on-rails-component"> (no data-immediate-hydration)
  Client->>Client: clientStartup() runs
  Client->>Pro: getRailsContext() -> { rorPro: true/false }
  alt rorPro == true
    Client->>Renderer: renderOrHydrateCompleteComponents() / hydrateCompleteStores()
    Renderer->>Client: hydrate/render as streaming scripts complete
  else rorPro == false
    Client->>Client: wait for DOMContentLoaded
    Client->>Renderer: renderOrHydrateAllComponents() / hydrateAllStores()
    Renderer->>Client: hydrate/render on DOMContentLoaded
  end
  Note right of Server: stream_react_component warns if passed :immediate_hydration
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

"I nibbled flags and tidied strands,
Removed a toggle with tiny hands.
Now Pro decides when scripts wake—
No more per-component give or take.
Hop, hop, hooray — a cleaner land! 🐇"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.94% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Remove immediate_hydration feature from codebase' accurately and concisely summarizes the main objective of this PR, which is to eliminate the immediate_hydration feature entirely.
Linked Issues check ✅ Passed The PR successfully meets all coding requirements from issue #2142: removes the immediate_hydration config option, helper/controller parameters, data-immediate-hydration attributes, normalization/validation logic, deprecated config accessor, per-component toggles, updates server/client helpers, and updates documentation and types.
Out of Scope Changes check ✅ Passed All changes are within scope of removing the immediate_hydration feature. The minor type narrowing in create-react-on-rails-app is directly related to Pro mode handling, which is part of the feature removal scope.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jg/2142-rm-immediate-hydration

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.

Comment thread react_on_rails_pro/app/helpers/react_on_rails_pro_helper.rb Outdated
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 24, 2026

Review: Remove immediate_hydration feature

Overall this is a clean, well-scoped removal. The logic simplification is welcome and the test coverage cleanup correctly mirrors what is being deleted. A few things worth addressing:

Stale comment in ClientSideRenderer.ts

Line 271 (inside the forAllCompleteElementsAsync JSDoc) still reads: 'Elements without a nextSibling will be hydrated later when their immediate hydration script executes and calls reactOnRailsComponentLoaded().' Since the per-component opt-in script is gone, 'immediate hydration script' should be updated (e.g. 'inline Pro hydration script') to stay accurate.

react_component silently ignores leftover immediate_hydration option

redux_store (helper + controller) removed the explicit named parameter, so passing immediate_hydration: there will correctly raise ArgumentError: unknown keyword. However react_component takes a generic options = {} hash, so callers who still pass immediate_hydration: true get no error and no warning — the key is just never read. Worth adding a one-time deprecation warn in RenderOptions or the helper so apps get a clear signal during the upgrade window.

Behaviour change scope for renderOrHydrateCompleteComponents

The old function selected only [data-immediate-hydration=true] elements. The new one selects ALL .js-react-on-rails-component elements that have a nextSibling. For Pro users every complete component is now eligible for pre-page-load hydration regardless of the original opt-in. The CHANGELOG covers the intent, but a sentence explicitly noting this all-or-nothing shift would help upgraders understand the broader scope.

Minor: changelog section order

The new Changed section was inserted above the existing Added section. Keep-a-Changelog convention orders: Added -> Changed -> Deprecated -> Removed. Swapping the two sections would keep it consistent.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Mar 24, 2026

Greptile Summary

This PR completes the removal of the immediate_hydration per-component toggle introduced in 16.2.0, simplifying the model to: Pro users always get immediate hydration, non-Pro users never do — no per-component override. All related Ruby API parameters, HTML data attributes, TypeScript selectors, and normalisation/validation logic are cleanly excised across 23 files.

Key changes:

  • renderOrHydrateImmediateHydratedComponents / hydrateImmediateHydratedStores renamed to renderOrHydrateCompleteComponents / hydrateCompleteStores, now selecting all .js-react-on-rails-component elements (not just those with the removed attribute) — effectively enabling immediate hydration for every Pro-user component that is complete in the DOM when the bundle executes
  • generate_component_script / generate_store_script gate the inline hydration <script> on ReactOnRails::Utils.react_on_rails_pro? instead of a per-render flag
  • stream_react_component now deletes any user-supplied immediate_hydration key from options, gracefully swallowing legacy callers rather than erroring
  • redux_store (both helper and controller) had the immediate_hydration: keyword argument removed — callers still passing it will receive a hard ArgumentError; this is intentional and documented in the CHANGELOG
  • Two test-support files (streaming_race_simulator.rb, json_parse_race_condition.spec.ts) were not part of this PR's changeset but still contain stale comments that reference immediate_hydration as a per-component feature — minor follow-up cleanup recommended

Confidence Score: 5/5

  • Safe to merge — the removal is thorough, all impacted layers (Ruby helpers, RBS types, TypeScript selectors, specs) are consistently updated, and the only open items are two stale comments in out-of-scope test support files.
  • Every code path that previously read or wrote immediate_hydration has been cleaned up. The new "always-on for Pro" behaviour is structurally simpler and the existing unit tests (160 passing) and TypeScript type-check confirm internal consistency. The two P2 style comments about stale descriptions in out-of-scope files do not affect runtime behaviour.
  • react_on_rails_pro/spec/dummy/lib/streaming_race_simulator.rb and react_on_rails_pro/spec/dummy/e2e-tests/json_parse_race_condition.spec.ts have stale comments referencing the removed feature, but are not part of this PR's changeset.

Important Files Changed

Filename Overview
packages/react-on-rails-pro/src/ClientSideRenderer.ts Removed data-immediate-hydration attribute check, the Pro warning constant, and onPageLoaded fallback. Renamed exported functions to drop the "ImmediateHydrated" naming. The forAllCompleteElementsAsync selector now targets all .js-react-on-rails-component elements (not just those with the attribute), correctly implementing the "always-on for Pro" behavior.
react_on_rails/lib/react_on_rails/utils.rb Removed normalize_immediate_hydration and immediate_hydration_pro_install_warning helpers entirely. Clean removal with corresponding spec deletion.
react_on_rails/lib/react_on_rails/configuration.rb Removed deprecated immediate_hydration getter/setter with their mutex-guarded one-time warning logic. Clean removal matching the RBS signature update.
react_on_rails/lib/react_on_rails/pro_helper.rb Both generate_component_script and generate_store_script now gate the inline hydration script on ReactOnRails::Utils.react_on_rails_pro? instead of a per-render immediate_hydration flag. The data-immediate-hydration HTML attribute is no longer emitted.
react_on_rails_pro/app/helpers/react_on_rails_pro_helper.rb In stream_react_component, replaced the default-set options.merge(immediate_hydration: true) with options.delete(:immediate_hydration) — user-supplied values are now silently dropped before the options reach RenderOptions, and Pro-ness is determined globally at render time.
react_on_rails/lib/react_on_rails/helper.rb Removed immediate_hydration: keyword arg from redux_store. Callers passing this kwarg will now receive a hard ArgumentError, which is an intentional breaking change documented in the CHANGELOG.
react_on_rails/lib/react_on_rails/controller.rb Removed immediate_hydration: keyword arg and normalize_immediate_hydration call from the controller's redux_store. Consistent with helper.rb change.

Sequence Diagram

sequenceDiagram
    participant Browser
    participant JSBundle as JS Bundle (Pro)
    participant ReactOnRails as ReactOnRails Client
    participant DOM

    Note over Browser,DOM: Old flow (with immediate_hydration flag)
    Browser->>DOM: Parse HTML stream
    DOM-->>Browser: Component script tag with data-immediate-hydration="true"
    JSBundle->>ReactOnRails: clientStartup()
    ReactOnRails->>DOM: Query [data-immediate-hydration="true"] components
    DOM-->>ReactOnRails: Only flagged components
    ReactOnRails->>Browser: Hydrate flagged components immediately
    Browser->>ReactOnRails: DOMContentLoaded
    ReactOnRails->>DOM: Query ALL .js-react-on-rails-component
    ReactOnRails->>Browser: Hydrate remaining components

    Note over Browser,DOM: New flow (Pro always-on)
    Browser->>DOM: Parse HTML stream
    JSBundle->>ReactOnRails: clientStartup()
    ReactOnRails->>DOM: Query ALL .js-react-on-rails-component (nextSibling != null)
    DOM-->>ReactOnRails: All complete components
    ReactOnRails->>Browser: Hydrate all complete components immediately
    Browser->>ReactOnRails: DOMContentLoaded
    ReactOnRails->>DOM: Query ALL .js-react-on-rails-component
    ReactOnRails->>Browser: Hydrate any remaining components
Loading

Comments Outside Diff (2)

  1. react_on_rails_pro/spec/dummy/lib/streaming_race_simulator.rb, line 7-12 (link)

    P2 Stale immediate_hydration references in comments

    These comments still describe the removed immediate_hydration feature as the trigger for the race condition behavior. Since immediate hydration is now always on for Pro users (not a togglable feature), the comment should be updated to remove the per-component framing.

    The description on line 10 — "When immediate_hydration is enabled (default for Pro users)" — implies it can be disabled, which is no longer the case.

  2. react_on_rails_pro/spec/dummy/e2e-tests/json_parse_race_condition.spec.ts, line 1-5 (link)

    P2 Stale immediate_hydration reference in comment

    The description "race condition fix for immediate_hydration" is outdated — immediate hydration is no longer a per-component flag. The comment should describe it as a race condition fix for Pro users' immediate hydration behavior instead.

Reviews (1): Last reviewed commit: "Remove immediate_hydration feature from ..." | Re-trigger Greptile

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 24, 2026

size-limit report 📦

Path Size
react-on-rails/client bundled (gzip) 62.63 KB (0%)
react-on-rails/client bundled (gzip) (time) 62.63 KB (0%)
react-on-rails/client bundled (brotli) 53.7 KB (0%)
react-on-rails/client bundled (brotli) (time) 53.7 KB (0%)
react-on-rails-pro/client bundled (gzip) 63.56 KB (-0.14% 🔽)
react-on-rails-pro/client bundled (gzip) (time) 63.56 KB (-0.14% 🔽)
react-on-rails-pro/client bundled (brotli) 54.6 KB (-0.12% 🔽)
react-on-rails-pro/client bundled (brotli) (time) 54.6 KB (-0.12% 🔽)
registerServerComponent/client bundled (gzip) 127.39 KB (-0.06% 🔽)
registerServerComponent/client bundled (gzip) (time) 127.39 KB (-0.06% 🔽)
registerServerComponent/client bundled (brotli) 61.52 KB (-0.21% 🔽)
registerServerComponent/client bundled (brotli) (time) 61.52 KB (-0.21% 🔽)
wrapServerComponentRenderer/client bundled (gzip) 122.02 KB (0%)
wrapServerComponentRenderer/client bundled (gzip) (time) 122.02 KB (0%)
wrapServerComponentRenderer/client bundled (brotli) 56.77 KB (0%)
wrapServerComponentRenderer/client bundled (brotli) (time) 56.77 KB (0%)

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
react_on_rails/spec/dummy/spec/helpers/react_on_rails_helper_spec.rb (1)

430-451: Test logic correctly validates Pro-only hydration behavior.

The tests properly verify that the inline hydration script is present only when react_on_rails_pro? returns true.

Consider using named subjects for better test readability, as per coding guidelines:

♻️ Suggested improvement for named subjects
     describe "Pro inline hydration script" do
       let(:hydration_script) do
         %(typeof ReactOnRails === 'object' && ReactOnRails.reactOnRailsComponentLoaded('App-react-component-0');)
           .html_safe
       end

       context "with Pro gem installed" do
-        subject { react_component("App") }
+        subject(:rendered_component) { react_component("App") }

-        it { is_expected.to include hydration_script }
+        it { expect(rendered_component).to include hydration_script }
       end

       context "without Pro gem installed" do
-        subject { react_component("App") }
+        subject(:rendered_component) { react_component("App") }

         before do
           allow(ReactOnRails::Utils).to receive(:react_on_rails_pro?).and_return(false)
         end

-        it { is_expected.not_to include hydration_script }
+        it { expect(rendered_component).not_to include hydration_script }
       end
     end

As per coding guidelines: "Use named subjects in RSpec tests (e.g., subject(:method_result) { ... } instead of unnamed subject { ... })"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@react_on_rails/spec/dummy/spec/helpers/react_on_rails_helper_spec.rb` around
lines 430 - 451, Replace unnamed subjects with named subjects to improve
readability: in the "Pro inline hydration script" examples change the anonymous
subject { react_component("App") } to a named subject (e.g.,
subject(:component_html) { react_component("App") }) and update the examples to
use that name in expectations; keep references to hydration_script and to
mocking ReactOnRails::Utils.react_on_rails_pro? as-is so it’s clear which
behavior is being tested.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CHANGELOG.md`:
- Around line 27-29: Update the CHANGELOG entry to mention that removal of
immediate_hydration affects initializer, helper calls, and the controller API by
noting that react_on_rails/lib/react_on_rails/controller.rb drops the
immediate_hydration: keyword from redux_store (reference immediate_hydration and
redux_store), and append the standard PR/author footer formatted as
“[PR_NUMBER](PR_url) by [username](user_url)” (no hash before PR number) to
match other entries.

---

Nitpick comments:
In `@react_on_rails/spec/dummy/spec/helpers/react_on_rails_helper_spec.rb`:
- Around line 430-451: Replace unnamed subjects with named subjects to improve
readability: in the "Pro inline hydration script" examples change the anonymous
subject { react_component("App") } to a named subject (e.g.,
subject(:component_html) { react_component("App") }) and update the examples to
use that name in expectations; keep references to hydration_script and to
mocking ReactOnRails::Utils.react_on_rails_pro? as-is so it’s clear which
behavior is being tested.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 77ed90ba-6a3f-4209-a952-c82a4c76e3d8

📥 Commits

Reviewing files that changed from the base of the PR and between c8267da and ced26b0.

📒 Files selected for processing (23)
  • CHANGELOG.md
  • packages/react-on-rails-pro/src/ClientSideRenderer.ts
  • packages/react-on-rails-pro/src/createReactOnRailsPro.ts
  • packages/react-on-rails/src/clientStartup.ts
  • react_on_rails/lib/react_on_rails/configuration.rb
  • react_on_rails/lib/react_on_rails/controller.rb
  • react_on_rails/lib/react_on_rails/doctor.rb
  • react_on_rails/lib/react_on_rails/helper.rb
  • react_on_rails/lib/react_on_rails/pro_helper.rb
  • react_on_rails/lib/react_on_rails/react_component/render_options.rb
  • react_on_rails/lib/react_on_rails/utils.rb
  • react_on_rails/sig/react_on_rails/configuration.rbs
  • react_on_rails/sig/react_on_rails/controller.rbs
  • react_on_rails/spec/dummy/app/views/pages/turbo_stream_send_hello_world.turbo_stream.erb
  • react_on_rails/spec/dummy/e2e/playwright/e2e/react_on_rails/basic_components.spec.js
  • react_on_rails/spec/dummy/spec/helpers/react_on_rails_helper_spec.rb
  • react_on_rails/spec/dummy/spec/support/selenium_logger.rb
  • react_on_rails/spec/react_on_rails/configuration_spec.rb
  • react_on_rails/spec/react_on_rails/react_component/render_options_spec.rb
  • react_on_rails/spec/react_on_rails/utils_spec.rb
  • react_on_rails_pro/app/helpers/react_on_rails_pro_helper.rb
  • react_on_rails_pro/spec/dummy/app/views/layouts/application.html.erb
  • react_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb
💤 Files with no reviewable changes (8)
  • react_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb
  • react_on_rails/lib/react_on_rails/configuration.rb
  • react_on_rails/spec/react_on_rails/react_component/render_options_spec.rb
  • react_on_rails/sig/react_on_rails/configuration.rbs
  • react_on_rails/lib/react_on_rails/react_component/render_options.rb
  • react_on_rails/spec/react_on_rails/configuration_spec.rb
  • react_on_rails/lib/react_on_rails/utils.rb
  • react_on_rails/spec/react_on_rails/utils_spec.rb

Comment thread CHANGELOG.md Outdated
@justin808
Copy link
Copy Markdown
Member Author

Addressed review feedback in 383a5f2:

Fixed:

  • Stale JSDoc in ClientSideRenderer.ts:270-271: Updated comment to remove reference to "immediate hydration script" — now correctly says elements without a nextSibling are hydrated on DOMContentLoaded.
  • CHANGELOG entry: Added redux_store immediate_hydration: keyword removal mention and PR/author footer.

Skipped (with rationale):

  • coderabbitai walkthrough — automated summary, no action needed.
  • greptile summary — automated summary, no action needed.
  • github-actions size-limit report — informational only, 0% change.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 24, 2026

Overall this is a clean, well-scoped removal. The logic simplification in both Ruby and TypeScript is a clear win, and the renamed JS exports (renderOrHydrateCompleteComponents / hydrateCompleteStores) accurately describe the new behavior.

Concern 1: Silent API break for view-layer callers

Users who currently pass immediate_hydration: true to react_component, react_component_hash, or redux_store in ERB views will get no runtime warning — the key is now just an unrecognized entry in the options hash and silently dropped. The deprecation cycle in place for config.immediate_hydration= (setter/getter with log warnings) was removed, but there is no equivalent warning path for the helper-level argument. At minimum, RenderOptions or the helpers should log a deprecation warning when the options hash contains immediate_hydration:. Without it, the CHANGELOG instruction to remove all immediate_hydration references is the only signal, and many users will miss it.

Concern 2: renderOrHydrateCompleteComponents widened scope

The old renderOrHydrateImmediateHydratedComponents only matched .js-react-on-rails-component[data-immediate-hydration="true"], so only opted-in components were hydrated before DOMContentLoaded. The new function matches ALL .js-react-on-rails-component elements that have a nextSibling. For Pro apps this means every structurally-complete component is hydrated eagerly, not just those that previously opted in. This is the intended direction, but it widens early-hydration surface area and could surface ordering issues (e.g., a store dependency not yet hydrated when a component tries to consume it). This behavioral change warrants a focused integration test.

Concern 3: CI integration tests still pending

The test-plan checkboxes for "CI: dummy app integration tests" and "CI: Pro integration tests" are unchecked. Given that this changes hydration ordering and the set of elements hydrated early, merging before those pass is risky. The deleted unit tests were well-targeted but do not cover the streaming/timing path end-to-end.

Minor: options.delete(:immediate_hydration) in stream_react_component lacks context

The silent delete protects against callers who still pass the old option — correct behavior — but a brief comment noting this is a backward-compat shim for a removed option would prevent the line from looking like dead code to future readers.

Positives: Removal of the mutex-protected immediate_hydration_warned class-level state from Configuration is a genuine simplification. Delegating the hydration decision entirely to react_on_rails_pro? rather than per-component state is cleaner and less error-prone. RBS signatures, test cleanup, and comment updates are all consistent with the code changes.

Comment thread react_on_rails_pro/app/helpers/react_on_rails_pro_helper.rb Outdated
Comment thread packages/react-on-rails-pro/src/ClientSideRenderer.ts
Comment thread react_on_rails/lib/react_on_rails/react_component/render_options.rb
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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

283-284: Please lock this selector broadening down with an integration test.

These two selectors are the client-side contract change for removing data-immediate-hydration. I don't see coverage in the provided context for a streamed component/store without the removed attribute hydrating before DOMContentLoaded, and that's the path most likely to regress.

Also applies to: 318-319

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-on-rails-pro/src/ClientSideRenderer.ts` around lines 283 -
284, Add an integration test that locks the new broad selector usage
('.js-react-on-rails-component') by verifying the client-side contract in
renderOrHydrateCompleteComponents (which calls forAllCompleteElementsAsync with
renderOrHydrateComponent) and the equivalent place at lines ~318-319: build a
fixture that simulates a streamed component/store element without the removed
data-immediate-hydration attribute, load the client bundle in a test harness
that can assert whether hydration occurs prior to dispatching DOMContentLoaded
(or simulate/document when DOMContentLoaded fires), and fail the test if the
element is not hydrated early; this ensures the selector change does not regress
streamed hydration behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/react-on-rails-pro/src/ClientSideRenderer.ts`:
- Around line 283-284: Add an integration test that locks the new broad selector
usage ('.js-react-on-rails-component') by verifying the client-side contract in
renderOrHydrateCompleteComponents (which calls forAllCompleteElementsAsync with
renderOrHydrateComponent) and the equivalent place at lines ~318-319: build a
fixture that simulates a streamed component/store element without the removed
data-immediate-hydration attribute, load the client bundle in a test harness
that can assert whether hydration occurs prior to dispatching DOMContentLoaded
(or simulate/document when DOMContentLoaded fires), and fail the test if the
element is not hydrated early; this ensures the selector change does not regress
streamed hydration behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dfe4116c-de45-4344-8bbe-fc9d25663cda

📥 Commits

Reviewing files that changed from the base of the PR and between ced26b0 and a946d87.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • packages/create-react-on-rails-app/src/create-app.ts
  • packages/create-react-on-rails-app/src/index.ts
  • packages/react-on-rails-pro/src/ClientSideRenderer.ts
✅ Files skipped from review due to trivial changes (2)
  • packages/create-react-on-rails-app/src/create-app.ts
  • packages/create-react-on-rails-app/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • CHANGELOG.md

@justin808 justin808 force-pushed the jg/2142-rm-immediate-hydration branch from a946d87 to da3e0db Compare March 25, 2026 04:02
@coderabbitai coderabbitai Bot added review-needed docs-cleanup Documentation cleanup or migration P3 Parked priority labels Mar 25, 2026
Comment thread react_on_rails_pro/app/helpers/react_on_rails_pro_helper.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/pro_helper.rb
Comment thread packages/react-on-rails-pro/src/ClientSideRenderer.ts
Comment thread packages/create-react-on-rails-app/src/create-app.ts
Comment thread CHANGELOG.md Outdated
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 25, 2026

Review: Remove immediate_hydration feature

The overall direction is correct and the implementation is clean. The deduplication concern (components processed by both renderOrHydrateCompleteComponents and then again by renderOrHydrateAllComponents) is safely handled by the renderedRoots Map in renderOrHydrateComponent. Good.

Summary of inline comments posted:

  • pro_helper.rb:130 (Medium) — options.delete(:immediate_hydration) silently discards caller-provided value with no deprecation warning. Pro users who passed immediate_hydration: false to opt out will now silently get the opposite behaviour.
  • CHANGELOG.md (Medium) — Entry is under "Changed" rather than "Removed"; no mention that per-component immediate_hydration: false opt-out is gone with no replacement.
  • pro_helper.rb:22 (Low) — generate_component_script unconditionally adds the inline script for all Pro components including non-streaming ones; a clarifying comment on intent/scope would help.
  • ClientSideRenderer.ts:283 (Low) — Wider selector on a fully-loaded (non-streaming) Pro page renders all components immediately and then the DOMContentLoaded handler hits nothing but cache. Correct, but a comment explaining the expected no-op on the second pass would help future readers.
  • create-app.ts:229 (Nit) — Type narrowing appears unrelated to this PR.

Key concern: Pro users who relied on immediate_hydration: false to disable early hydration for specific components/stores have no migration path. The CHANGELOG should explicitly state that per-component opt-out is removed with no replacement.

What is handled well:

  • renderedRoots / storeRenderers Maps prevent double-rendering when both early and DOMContentLoaded paths fire.
  • Mutex-based config.immediate_hydration deprecation warning cleanly removed.
  • utils.rbs correctly left alone (no explicit signature for normalize_immediate_hydration).
  • Test coverage updated consistently across all affected specs.
  • Unused onPageLoaded import correctly removed from ClientSideRenderer.ts.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
react_on_rails_pro/app/helpers/react_on_rails_pro_helper.rb (1)

126-130: ⚠️ Potential issue | 🟡 Minor

Normalize the removed option before cached paths see it.

Line 130 strips :immediate_hydration too late for the cached streaming flow: fetch_stream_react_component already derives the cache key from raw_options on Line 295. Upgraded callers that still pass the removed option will create separate cache entries for identical streamed output.

♻️ Proposed normalization
 def cached_stream_react_component(component_name, raw_options = {}, &block)
+  raw_options = raw_options.dup
+  raw_options.delete(:immediate_hydration)
+
   ReactOnRailsPro::Utils.with_trace(component_name) do
     check_caching_options!(raw_options, block)
     fetch_stream_react_component(component_name, raw_options, &block)
   end
 end

 def stream_react_component(component_name, options = {})
+  options = options.dup
+
   # stream_react_component doesn't have the prerender option
   # Because setting prerender to false is equivalent to calling react_component with prerender: false
   options[:prerender] = true
   options.delete(:immediate_hydration)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@react_on_rails_pro/app/helpers/react_on_rails_pro_helper.rb` around lines 126
- 130, The cached streaming flow creates cache keys from raw_options before the
helper strips :immediate_hydration, so callers that still pass that removed
option produce different cache entries; update stream_react_component (and/or
the code that builds raw_options used by fetch_stream_react_component) to
normalize options early by removing :immediate_hydration (and any other
deprecated keys) before the cache key is derived so identical streamed output
shares the same cache entry (reference: stream_react_component,
fetch_stream_react_component, raw_options).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@react_on_rails/lib/react_on_rails/pro_helper.rb`:
- Around line 41-44: The attribute value for "data-js-react-on-rails-store" is
being marked html_safe (redux_store_data[:store_name].html_safe), which can
bypass escaping and allow attribute/script injection; change the content_tag
invocation so the store name is passed without html_safe (e.g., use
redux_store_data[:store_name].to_s or the raw value) and let content_tag handle
escaping of attribute values; keep the script body handling
(json_safe_and_pretty(...).html_safe) as-is.

---

Outside diff comments:
In `@react_on_rails_pro/app/helpers/react_on_rails_pro_helper.rb`:
- Around line 126-130: The cached streaming flow creates cache keys from
raw_options before the helper strips :immediate_hydration, so callers that still
pass that removed option produce different cache entries; update
stream_react_component (and/or the code that builds raw_options used by
fetch_stream_react_component) to normalize options early by removing
:immediate_hydration (and any other deprecated keys) before the cache key is
derived so identical streamed output shares the same cache entry (reference:
stream_react_component, fetch_stream_react_component, raw_options).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e98cc500-e834-440a-92d9-718ab1020f0f

📥 Commits

Reviewing files that changed from the base of the PR and between a946d87 and da3e0db.

📒 Files selected for processing (24)
  • CHANGELOG.md
  • packages/create-react-on-rails-app/src/create-app.ts
  • packages/react-on-rails-pro/src/ClientSideRenderer.ts
  • packages/react-on-rails-pro/src/createReactOnRailsPro.ts
  • packages/react-on-rails/src/clientStartup.ts
  • react_on_rails/lib/react_on_rails/configuration.rb
  • react_on_rails/lib/react_on_rails/controller.rb
  • react_on_rails/lib/react_on_rails/doctor.rb
  • react_on_rails/lib/react_on_rails/helper.rb
  • react_on_rails/lib/react_on_rails/pro_helper.rb
  • react_on_rails/lib/react_on_rails/react_component/render_options.rb
  • react_on_rails/lib/react_on_rails/utils.rb
  • react_on_rails/sig/react_on_rails/configuration.rbs
  • react_on_rails/sig/react_on_rails/controller.rbs
  • react_on_rails/spec/dummy/app/views/pages/turbo_stream_send_hello_world.turbo_stream.erb
  • react_on_rails/spec/dummy/e2e/playwright/e2e/react_on_rails/basic_components.spec.js
  • react_on_rails/spec/dummy/spec/helpers/react_on_rails_helper_spec.rb
  • react_on_rails/spec/dummy/spec/support/selenium_logger.rb
  • react_on_rails/spec/react_on_rails/configuration_spec.rb
  • react_on_rails/spec/react_on_rails/react_component/render_options_spec.rb
  • react_on_rails/spec/react_on_rails/utils_spec.rb
  • react_on_rails_pro/app/helpers/react_on_rails_pro_helper.rb
  • react_on_rails_pro/spec/dummy/app/views/layouts/application.html.erb
  • react_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb
💤 Files with no reviewable changes (8)
  • react_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb
  • react_on_rails/lib/react_on_rails/react_component/render_options.rb
  • react_on_rails/spec/react_on_rails/utils_spec.rb
  • react_on_rails/lib/react_on_rails/configuration.rb
  • react_on_rails/spec/react_on_rails/configuration_spec.rb
  • react_on_rails/sig/react_on_rails/configuration.rbs
  • react_on_rails/spec/react_on_rails/react_component/render_options_spec.rb
  • react_on_rails/lib/react_on_rails/utils.rb
✅ Files skipped from review due to trivial changes (6)
  • packages/react-on-rails/src/clientStartup.ts
  • react_on_rails/spec/dummy/app/views/pages/turbo_stream_send_hello_world.turbo_stream.erb
  • react_on_rails_pro/spec/dummy/app/views/layouts/application.html.erb
  • packages/create-react-on-rails-app/src/create-app.ts
  • react_on_rails/lib/react_on_rails/helper.rb
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (5)
  • react_on_rails/spec/dummy/e2e/playwright/e2e/react_on_rails/basic_components.spec.js
  • react_on_rails/spec/dummy/spec/support/selenium_logger.rb
  • react_on_rails/lib/react_on_rails/controller.rb
  • react_on_rails/sig/react_on_rails/controller.rbs
  • react_on_rails/spec/dummy/spec/helpers/react_on_rails_helper_spec.rb

Comment thread react_on_rails/lib/react_on_rails/pro_helper.rb Outdated
Comment thread react_on_rails_pro/app/helpers/react_on_rails_pro_helper.rb Outdated
Comment thread packages/react-on-rails-pro/src/ClientSideRenderer.ts Outdated
Comment thread react_on_rails/lib/react_on_rails/pro_helper.rb
Comment thread react_on_rails_pro/app/helpers/react_on_rails_pro_helper.rb
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 6, 2026

Review: Remove immediate_hydration feature (#2834)

Overall this is a clean, well-scoped removal — the architecture is sound and the test coverage for the new behavior is solid. A few issues to address before merge:

Issues

1. RBS type signatures still include immediate_hydration (should be removed, not updated)

Both controller.rbs and helper.rbs were updated to add/keep immediate_hydration: bool? in their signatures. This is counterproductive — RBS/Steep type checkers will silently accept calls with immediate_hydration: as valid, defeating the purpose of the removal. The parameter should be dropped entirely from both signatures. See inline comments.

2. options.delete(:immediate_hydration) mutates caller's hash

In react_component and react_component_hash, calling options.delete(:immediate_hydration) mutates the hash passed in. If the same hash is reused across multiple calls (e.g., from a partial called in a loop), the key will be gone on the second call — and crucially, the deprecation warning won't fire on the second call either. Prefer options.except(:immediate_hydration) with reassignment. See inline comment.

3. Silent no-op in renderOrHydrateComponent when railsContext is missing

When reactOnRailsComponentLoaded is called via an inline script before js-react-on-rails-context has been parsed, the renderer is silently discarded and Promise.resolve() is returned. This is correct by design (the page-load sweep recovers), but completely invisible when debugging streaming hydration timing issues. A console.debug log in dev/test (matching the pattern already in createReactOnRailsPro.ts) would help. See inline comment.

4. CHANGELOG should note per-component opt-out is gone

The current changelog entry mentions the removal but doesn't explicitly state that immediate_hydration: false (the per-component opt-out) is no longer possible for Pro users. Pro apps that used immediate_hydration: false to delay hydration of expensive components will get a behavior change silently after the warning fires once. See inline comment on pro_helper.rb.

Minor observations

  • The doctor.rb rubocop disable/enable comment change correctly removes Metrics/MethodLength since the method got shorter — good cleanup.
  • StoreRenderer sets this.state = 'hydrating' before the railsContext null-check, leaving a renderer in 'hydrating' state with no hydratePromise. This is harmless since the renderer is never cached in that case, but slightly misleading. Low priority.
  • The warn_removed_immediate_hydration_option is process-global state (warns once per helper name per process lifetime). This is appropriate for a "remove this deprecated option" warning, and the test helper reset_removed_immediate_hydration_warnings! is correctly used in specs.

The immediate_hydration parameter was still present in the RBS type
signatures for controller.rbs and helper.rbs, which would let type
checkers silently accept calls with the removed option. Removing
them ensures Steep/RBS flag any lingering usage.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@justin808
Copy link
Copy Markdown
Member Author

Review address summary

I (Claude Code) addressed the latest round of review comments:

Fixed (2):

  • Removed immediate_hydration: bool? from controller.rbs and helper.rbs RBS signatures — type checkers will now correctly flag any lingering usage of the removed option (389ac3a)

Declined with rationale (3):

  • ClientSideRenderer.ts:253 console.debug suggestion — existing debugTurbolinks call already provides tracing; page-load sweep reliably recovers
  • helper.rb:87 options.except vs options.deletedelete is the standard Ruby pattern; reusing the same options hash across multiple calls is extremely rare in practice
  • pro_helper.rb:22 CHANGELOG migration note — already covered by the existing entry which explicitly states "with no per-component override"

All 5 review threads resolved.

this.state = 'hydrated';
}

hasStartedHydrating(): boolean {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

State inconsistency when railsContext is null

Both ComponentRenderer and StoreRenderer set this.state = 'rendering'/'hydrating' at the top of their constructors, then early-return when railsContext is null, leaving renderPromise/hydratePromise undefined. The hasStartedHydrating() / hasStartedRendering() guards in the callers correctly detect this and discard the instance — so it's benign in practice.

However, if waitUntilHydrated() were ever called on a discarded instance (e.g. by a future code path), it would check this.state === 'hydrating' && this.hydratePromise → the condition fails and it silently returns Promise.resolve() without hydrating anything. A small defensive improvement would be to initialise state to a distinct sentinel like 'idle' when railsContext is absent, so any such mistaken call would be more visible.

// (identified by having a nextSibling). On non-streaming pages this matches ALL components,
// but ClientSideRenderer memoizes by DOM node id so the later DOMContentLoaded sweep is a no-op.
export const renderOrHydrateCompleteComponents = () =>
forAllCompleteElementsAsync('.js-react-on-rails-component', renderOrHydrateComponent);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Behavior change: now sweeps ALL Pro components, not just data-immediate-hydration="true" ones

This is a meaningful semantic change: the old selector '.js-react-on-rails-component[data-immediate-hydration="true"]' only targeted components that were explicitly opted in. The new selector matches every rendered component.

On a non-streaming page, when clientStartup runs with railsContext available, forAllCompleteElementsAsync sees all components as complete (they all have a nextSibling) and triggers early hydration for every single one — previously this only happened for explicitly marked components.

This is the intended goal (all Pro users get immediate hydration automatically), but it should be verified that no existing Pro apps relied on selectively disabling immediate hydration for expensive components. The comment above the function explains the memoisation safety net, which is correct.

if (railsContext === null) {
// Context element not yet in DOM — expected in streaming scenarios.
// Early Pro hydration skipped; the page-loaded sweep will recover all components.
if (process.env.NODE_ENV !== 'production' && typeof console !== 'undefined') {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

typeof console !== 'undefined' is always true in a browser context — console is part of the Web API spec and guaranteed to exist. The check can be dropped:

Suggested change
if (process.env.NODE_ENV !== 'production' && typeof console !== 'undefined') {
if (process.env.NODE_ENV !== 'production') {

Comment on lines +15 to +16
def redux_store(store_name, props: {}, **rest)
immediate_hydration_present = rest.key?(:immediate_hydration)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Duplicated **rest validation between controller.rb and helper.rb

Lines 15–24 here are byte-for-byte identical to helper.rb lines 201–209 (which handles the same redux_store method on the view-helper side). Both files will need to be updated if a new removed keyword is ever added or the error message changes. Consider extracting to a shared private method, e.g. in ReactOnRails::Utils:

# utils.rb
def self.validate_no_unknown_kwargs(rest, known_removed: [])
  if rest.key?(:immediate_hydration) || known_removed.any? { |k| rest.key?(k) }
    ReactOnRails::Helper.warn_removed_immediate_hydration_option(...)
  end
  unknown_keys = rest.keys - known_removed
  if unknown_keys.any?
    plural = unknown_keys.one? ? "" : "s"
    raise ArgumentError, "unknown keyword#{plural}: #{unknown_keys.map { |k| ":#{k}" }.join(', ')}"
  end
end

Not a blocker, but worth a follow-up ticket to avoid drift.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 6, 2026

Review: Remove immediate_hydration feature

Overall this is a clean, well-scoped removal. The test coverage is thorough (new unit tests for the discard-before-cache pattern, warning deduplication, and store-name escaping), the RBS signatures are kept in sync, and the migration path for existing users is clearly documented in CHANGELOG.md.

Security fix (positive)

The removal of .html_safe from redux_store_data[:store_name] in pro_helper.rb is an important security improvement — it lets Rails properly HTML-escape the attribute value and closes a potential attribute-injection vector. The new spec "escapes store_name when serializing data-js-react-on-rails-store" locks this in.

Behavioral notes

All Pro components now get immediate hydration unconditionally. Previously users could opt specific components in via immediate_hydration: true (and opt others out via false). Now the Pro code path always hydrates early. The inline comment on renderOrHydrateCompleteComponents captures the memoisation reasoning, but apps that relied on selectively disabling immediate hydration for expensive components will silently change behavior without any warning.

Streaming + non-streaming parity. The railsContext === null fast-path in clientStartup correctly falls back to the DOMContentLoaded sweep, so streaming pages where the context element isn't yet in the DOM are handled gracefully.

Minor issues (inline comments)

  1. State inconsistency in renderer constructorsStoreRenderer / ComponentRenderer set this.state = 'hydrating' before the railsContext null-guard, leaving a discarded instance with state = 'hydrating' but hydratePromise = undefined. Benign today, but a defensive sentinel state ('idle') would make any future misuse more visible.

  2. typeof console !== 'undefined' — always true in browsers; the check can be removed (see inline).

  3. Duplicated **rest validationcontroller.rb and helper.rb both contain identical 9-line blocks for validating and warning on unknown keyword args. Worth extracting to avoid future drift (see inline).

Not a concern

  • Thread safety of warn_removed_immediate_hydration_option: the per-helper-name mutex pattern is correct; next false inside synchronize correctly returns false from the block.
  • Memoisation of renderedRoots/storeRenderers correctly prevents double-hydration when the early sweep and the DOMContentLoaded sweep both run on non-streaming pages.

@justin808 justin808 merged commit 13e2b23 into main Apr 7, 2026
46 checks passed
@justin808 justin808 deleted the jg/2142-rm-immediate-hydration branch April 7, 2026 00:17
justin808 added a commit that referenced this pull request Apr 8, 2026
…e-dedup

* origin/main: (51 commits)
  Fix Content-Length mismatch and null renderingRequest errors in node renderer (#3069)
  Improve memory debugging docs with simpler heap snapshot approach (#3072)
  Enforce strict version matching in doctor and recommend doctor on errors (#3070)
  Remove immediate_hydration feature from codebase (#2834)
  Fix infinite fork loop when node renderer worker fails to bind port (#2881)
  Fix TanStack Router SSR hydration mismatch in Pro async path (#2932)
  Improve node renderer error messages for malformed render requests (#3068)
  Add interactive mode prompt to create-react-on-rails-app (#3063)
  docs: replace hardcoded version numbers with unversioned install commands (#2893)
  Remove Contributing section from docs sidebar (#3064)
  Consolidate docs comparison pages into single evaluation entry (#3065)
  Docs route cleanup: canonicalize worst verbose URL slugs (#3067)
  Add agent summary blocks to high-value docs pages (#3066)
  Fix brittle positional assertions in create-app tests (#2923)
  Auto-resolve renderer password from ENV in Rails Pro (#2921)
  Standardize bundle env vars from =yes to =true (#2925)
  Fix Pro generator multiline and template-literal rewrites (#2918)
  Fix spec/dummy Procfile.dev SERVER_BUNDLE_ONLY to match template convention (#2922)
  Add llms docs entry points (#2916)
  Bump version to 16.6.0.rc.0
  ...

# Conflicts:
#	packages/create-react-on-rails-app/src/create-app.ts
justin808 added a commit that referenced this pull request Apr 8, 2026
…lacement-2835-main

* origin/main: (44 commits)
  Fix Content-Length mismatch and null renderingRequest errors in node renderer (#3069)
  Improve memory debugging docs with simpler heap snapshot approach (#3072)
  Enforce strict version matching in doctor and recommend doctor on errors (#3070)
  Remove immediate_hydration feature from codebase (#2834)
  Fix infinite fork loop when node renderer worker fails to bind port (#2881)
  Fix TanStack Router SSR hydration mismatch in Pro async path (#2932)
  Improve node renderer error messages for malformed render requests (#3068)
  Add interactive mode prompt to create-react-on-rails-app (#3063)
  docs: replace hardcoded version numbers with unversioned install commands (#2893)
  Remove Contributing section from docs sidebar (#3064)
  Consolidate docs comparison pages into single evaluation entry (#3065)
  Docs route cleanup: canonicalize worst verbose URL slugs (#3067)
  Add agent summary blocks to high-value docs pages (#3066)
  Fix brittle positional assertions in create-app tests (#2923)
  Auto-resolve renderer password from ENV in Rails Pro (#2921)
  Standardize bundle env vars from =yes to =true (#2925)
  Fix Pro generator multiline and template-literal rewrites (#2918)
  Fix spec/dummy Procfile.dev SERVER_BUNDLE_ONLY to match template convention (#2922)
  Add llms docs entry points (#2916)
  Bump version to 16.6.0.rc.0
  ...

# Conflicts:
#	CHANGELOG.md
@justin808 justin808 mentioned this pull request Apr 8, 2026
2 tasks
justin808 added a commit that referenced this pull request Apr 8, 2026
### Summary

Adds missing changelog entries to the `[Unreleased]` section for PRs
merged since `v16.6.0.rc.0`. Analyzed 19 commits on `origin/main` since
the last tag, identified 1 missing user-visible PR (#3069), and skipped
10 non-user-visible PRs (docs, tests, internal refactors).

**New entries added:**
- **Added**: `[Pro] Configurable HTTP keep-alive timeout for node
renderer connections` (PR #3069)
- **Fixed**: `[Pro] Fixed SSR failures from stale persistent HTTP/2
connections` (PR #3069)

**Entries already present** (added in prior changelog updates): PRs
#2834, #2881, #2918, #2921, #2923, #2932, #3063, #3068, #3070.

**Skipped** (not user-visible): #2893 (docs), #2916 (docs), #2922 (test
fix), #2923 (test fix), #2925 (internal refactor), #3064 (docs), #3065
(docs), #3066 (docs), #3067 (docs), #3072 (docs).

### Pull Request checklist

- [x] ~Add/update test to cover these changes~
- [x] ~Update documentation~
- [x] Update CHANGELOG file

### Other Information

No code changes — CHANGELOG.md only.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk because this PR only changes documentation (`CHANGELOG.md`)
and does not modify runtime code.
> 
> **Overview**
> Updates `CHANGELOG.md` *[Unreleased]* to include missing Pro release
notes for PR `#3069`, documenting the new
`renderer_http_keep_alive_timeout` config and the associated fix for SSR
failures from stale persistent HTTP/2 connections to the node renderer.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
8d1a480. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Interactive mode selection prompt for CLI tool when no explicit mode
is specified
* Configurable keep-alive timeout setting for Pro users (default 30
seconds)

* **Bug Fixes**
  * Enhanced validation and error handling for invalid request payloads
  * Improved template literal handling in code generation
  * Better HTTP connection stability with enhanced diagnostic messaging

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
justin808 added a commit that referenced this pull request Apr 12, 2026
## Summary

- Removes the `immediate_hydration` configuration option, helper
parameter, `data-immediate-hydration` HTML attribute, and all related
normalization/validation logic
- Immediate hydration is now always enabled for Pro users and disabled
for non-Pro users — no per-component toggle
- The `config.immediate_hydration` deprecated setter/getter (added in
16.2.0) is fully removed
- Pro `stream_react_component` no longer injects `immediate_hydration:
true`; Pro inline hydration scripts are now unconditionally added based
on `react_on_rails_pro?`
- JS Pro package: renamed `renderOrHydrateImmediateHydratedComponents` →
`renderOrHydrateCompleteComponents` (now selects all components, not
just those with the removed attribute)

Closes #2142

## Test plan

- [x] RuboCop: 0 offenses across all changed files (173 inspected)
- [x] RBS validation passed
- [x] TypeScript type-check passed (all 4 packages)
- [x] Gem unit tests: 160 examples, 0 failures
- [ ] CI: dummy app integration tests (helper spec, E2E)
- [ ] CI: Pro integration tests


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

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Medium risk because it removes a previously supported
configuration/keyword option and changes client hydration triggering
logic for streaming/non-streaming pages, which could affect render
timing or cause missed hydration if edge cases exist.
> 
> **Overview**
> **Removes the `immediate_hydration` feature end-to-end**: drops
`config.immediate_hydration`, per-helper keyword args, and the
`data-immediate-hydration` DOM attribute, with Pro now hydrating early
automatically and non-Pro hydrating on page load (no per-component
override).
> 
> **Updates Pro streaming hydration** to hydrate any *complete* streamed
component/store (nextSibling-present) rather than filtering by the
removed attribute, and avoids caching renderers created before
`railsContext` exists; `stream_react_component` now warns if
`immediate_hydration:` is provided.
> 
> **Hardens/cleans up behavior and tests**: adds one-time warnings when
removed options are passed, tightens keyword-arg validation for
`redux_store`, fixes HTML attribute escaping for store names to prevent
attribute injection, and updates/extends Ruby, TS, and dummy app
tests/docs accordingly.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
389ac3a. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
* Removed the immediate_hydration option and per-component HTML
attribute; immediate hydration is now Pro‑gated (enabled for Pro,
disabled otherwise).
* **Bug Fixes / Behavior**
* Passing immediate_hydration to streaming calls now emits a warning;
store name serialization escaping fixed to prevent attribute injection.
* **Documentation**
* Changelog and inline docs updated to reflect the new Pro‑gated
hydration behavior.
* **Tests**
* Tests updated to reflect removed option and to verify store-name
escaping.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
justin808 added a commit that referenced this pull request Apr 12, 2026
### Summary

Adds missing changelog entries to the `[Unreleased]` section for PRs
merged since `v16.6.0.rc.0`. Analyzed 19 commits on `origin/main` since
the last tag, identified 1 missing user-visible PR (#3069), and skipped
10 non-user-visible PRs (docs, tests, internal refactors).

**New entries added:**
- **Added**: `[Pro] Configurable HTTP keep-alive timeout for node
renderer connections` (PR #3069)
- **Fixed**: `[Pro] Fixed SSR failures from stale persistent HTTP/2
connections` (PR #3069)

**Entries already present** (added in prior changelog updates): PRs
#2834, #2881, #2918, #2921, #2923, #2932, #3063, #3068, #3070.

**Skipped** (not user-visible): #2893 (docs), #2916 (docs), #2922 (test
fix), #2923 (test fix), #2925 (internal refactor), #3064 (docs), #3065
(docs), #3066 (docs), #3067 (docs), #3072 (docs).

### Pull Request checklist

- [x] ~Add/update test to cover these changes~
- [x] ~Update documentation~
- [x] Update CHANGELOG file

### Other Information

No code changes — CHANGELOG.md only.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk because this PR only changes documentation (`CHANGELOG.md`)
and does not modify runtime code.
> 
> **Overview**
> Updates `CHANGELOG.md` *[Unreleased]* to include missing Pro release
notes for PR `#3069`, documenting the new
`renderer_http_keep_alive_timeout` config and the associated fix for SSR
failures from stale persistent HTTP/2 connections to the node renderer.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
8d1a480. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Interactive mode selection prompt for CLI tool when no explicit mode
is specified
* Configurable keep-alive timeout setting for Pro users (default 30
seconds)

* **Bug Fixes**
  * Enhanced validation and error handling for invalid request payloads
  * Improved template literal handling in code generation
  * Better HTTP connection stability with enhanced diagnostic messaging

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
justin808 added a commit that referenced this pull request Apr 12, 2026
## Summary

- Removes the `immediate_hydration` configuration option, helper
parameter, `data-immediate-hydration` HTML attribute, and all related
normalization/validation logic
- Immediate hydration is now always enabled for Pro users and disabled
for non-Pro users — no per-component toggle
- The `config.immediate_hydration` deprecated setter/getter (added in
16.2.0) is fully removed
- Pro `stream_react_component` no longer injects `immediate_hydration:
true`; Pro inline hydration scripts are now unconditionally added based
on `react_on_rails_pro?`
- JS Pro package: renamed `renderOrHydrateImmediateHydratedComponents` →
`renderOrHydrateCompleteComponents` (now selects all components, not
just those with the removed attribute)

Closes #2142

## Test plan

- [x] RuboCop: 0 offenses across all changed files (173 inspected)
- [x] RBS validation passed
- [x] TypeScript type-check passed (all 4 packages)
- [x] Gem unit tests: 160 examples, 0 failures
- [ ] CI: dummy app integration tests (helper spec, E2E)
- [ ] CI: Pro integration tests


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

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Medium risk because it removes a previously supported
configuration/keyword option and changes client hydration triggering
logic for streaming/non-streaming pages, which could affect render
timing or cause missed hydration if edge cases exist.
> 
> **Overview**
> **Removes the `immediate_hydration` feature end-to-end**: drops
`config.immediate_hydration`, per-helper keyword args, and the
`data-immediate-hydration` DOM attribute, with Pro now hydrating early
automatically and non-Pro hydrating on page load (no per-component
override).
> 
> **Updates Pro streaming hydration** to hydrate any *complete* streamed
component/store (nextSibling-present) rather than filtering by the
removed attribute, and avoids caching renderers created before
`railsContext` exists; `stream_react_component` now warns if
`immediate_hydration:` is provided.
> 
> **Hardens/cleans up behavior and tests**: adds one-time warnings when
removed options are passed, tightens keyword-arg validation for
`redux_store`, fixes HTML attribute escaping for store names to prevent
attribute injection, and updates/extends Ruby, TS, and dummy app
tests/docs accordingly.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
389ac3a. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
* Removed the immediate_hydration option and per-component HTML
attribute; immediate hydration is now Pro‑gated (enabled for Pro,
disabled otherwise).
* **Bug Fixes / Behavior**
* Passing immediate_hydration to streaming calls now emits a warning;
store name serialization escaping fixed to prevent attribute injection.
* **Documentation**
* Changelog and inline docs updated to reflect the new Pro‑gated
hydration behavior.
* **Tests**
* Tests updated to reflect removed option and to verify store-name
escaping.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
justin808 added a commit that referenced this pull request Apr 12, 2026
### Summary

Adds missing changelog entries to the `[Unreleased]` section for PRs
merged since `v16.6.0.rc.0`. Analyzed 19 commits on `origin/main` since
the last tag, identified 1 missing user-visible PR (#3069), and skipped
10 non-user-visible PRs (docs, tests, internal refactors).

**New entries added:**
- **Added**: `[Pro] Configurable HTTP keep-alive timeout for node
renderer connections` (PR #3069)
- **Fixed**: `[Pro] Fixed SSR failures from stale persistent HTTP/2
connections` (PR #3069)

**Entries already present** (added in prior changelog updates): PRs
#2834, #2881, #2918, #2921, #2923, #2932, #3063, #3068, #3070.

**Skipped** (not user-visible): #2893 (docs), #2916 (docs), #2922 (test
fix), #2923 (test fix), #2925 (internal refactor), #3064 (docs), #3065
(docs), #3066 (docs), #3067 (docs), #3072 (docs).

### Pull Request checklist

- [x] ~Add/update test to cover these changes~
- [x] ~Update documentation~
- [x] Update CHANGELOG file

### Other Information

No code changes — CHANGELOG.md only.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Low risk because this PR only changes documentation (`CHANGELOG.md`)
and does not modify runtime code.
> 
> **Overview**
> Updates `CHANGELOG.md` *[Unreleased]* to include missing Pro release
notes for PR `#3069`, documenting the new
`renderer_http_keep_alive_timeout` config and the associated fix for SSR
failures from stale persistent HTTP/2 connections to the node renderer.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
8d1a480. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Interactive mode selection prompt for CLI tool when no explicit mode
is specified
* Configurable keep-alive timeout setting for Pro users (default 30
seconds)

* **Bug Fixes**
  * Enhanced validation and error handling for invalid request payloads
  * Improved template literal handling in code generation
  * Better HTTP connection stability with enhanced diagnostic messaging

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
justin808 added a commit that referenced this pull request Apr 18, 2026
The `immediate_hydration` option was removed from the codebase in #2834.
The behavior it controlled (Pro users hydrate components before
DOMContentLoaded) still exists, but it's now always-on for Pro and
always-off for OSS — there is no per-component toggle.

Several docs still showed `immediate_hydration: true/false` in code
examples or advised users to "enable" the option. This reframes those
sections as descriptions of Pro's built-in early hydration behavior and
drops the stale code examples.

Files updated:
- docs/pro/streaming-ssr.md — rewrite "Immediate Hydration" as "Early
  Hydration (Pro)", remove code example disabling the option
- docs/pro/troubleshooting.md — drop the stale bullet
- docs/oss/building-features/turbolinks.md — reframe Turbo Streams and
  async+Turbolinks sections; remove `immediate_hydration: true` from
  code examples
- docs/oss/api-reference/ruby-api-pro.md — remove `:immediate_hydration`
  from stream_react_component options

Fixes #3139
justin808 added a commit that referenced this pull request Apr 18, 2026
The `immediate_hydration` option was removed from the codebase in #2834.
The behavior it controlled (Pro users hydrate components before
DOMContentLoaded) still exists, but it's now always-on for Pro and
always-off for OSS — there is no per-component toggle.

Several docs still showed `immediate_hydration: true/false` in code
examples or advised users to "enable" the option. This reframes those
sections as descriptions of Pro's built-in early hydration behavior and
drops the stale code examples.

Files updated:
- docs/pro/streaming-ssr.md — rewrite "Immediate Hydration" as "Early
  Hydration (Pro)", remove code example disabling the option
- docs/pro/troubleshooting.md — drop the stale bullet
- docs/oss/building-features/turbolinks.md — reframe Turbo Streams and
  async+Turbolinks sections; remove `immediate_hydration: true` from
  code examples
- docs/oss/api-reference/ruby-api-pro.md — remove `:immediate_hydration`
  from stream_react_component options

Fixes #3139
justin808 added a commit that referenced this pull request Apr 18, 2026
The troubleshooting entry for the immediate_hydration deprecation warning
told users to "use component-level overrides if needed," but per-component
immediate_hydration: keys have been no-ops since #2834 and configuration-
deprecated.md now says to delete them.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
justin808 added a commit that referenced this pull request Apr 19, 2026
## Summary

The `immediate_hydration` option was removed from the codebase in #2834.
The behavior it controlled (Pro users hydrate components before
`DOMContentLoaded`) still exists, but it's now always-on for Pro and
always-off for OSS — there is no per-component toggle.

Several docs still presented `immediate_hydration` as a configurable
option, which confused users who tried to set it and found it didn't
exist. This PR reframes those sections as descriptions of Pro's built-in
early hydration behavior and drops stale code examples.

### Files updated

- `docs/pro/streaming-ssr.md` — renamed "Immediate Hydration" to "Early
Hydration (Pro)"; reworded surrounding prose; removed the
`immediate_hydration: false` code example; updated migration-timeline
bullet
- `docs/pro/troubleshooting.md` — removed the "Verify
`immediate_hydration` option" bullet from the hydration-failure
checklist
- `docs/oss/building-features/turbolinks.md` — reframed the Turbo
Streams section (Pro's early hydration is automatic, not a per-call
option); removed `immediate_hydration: true` from the code example;
reworded the async+Turbolinks warning box; removed the v16.0 migration
note that referenced the removed option; fixed GitHub `[!WARNING]` alert
syntax so the callout renders
- `docs/oss/api-reference/ruby-api-pro.md` — removed
`:immediate_hydration` from the `stream_react_component` options list
and split the options/hydration note for clarity
- `docs/oss/configuration/configuration-deprecated.md` — tightened the
migration step (delete both config and per-component
`immediate_hydration:` keys — all are no-ops) and corrected the removal
version to v16.6.0
- `docs/oss/upgrading/release-notes/16.2.0.md` — dropped the stale "use
component-level overrides if needed" hint from the troubleshooting
section (per-component keys were also removed in v16.6.0). The
historical "Component-level overrides still work" section is preserved
as-is since it accurately describes behavior at the 16.2.0 release
point.

### Files intentionally left alone

Per the issue, release notes and migration guidance still correctly
reference `immediate_hydration` as a historical/deprecated option:
- `docs/oss/upgrading/release-notes/16.0.0.md`
- `docs/oss/configuration/README.md`
- `docs/oss/upgrading/upgrading-react-on-rails.md` (migration guidance)

Fixes #3139

## Test plan

- [x] `grep -rn immediate_hydration docs/pro docs/oss/building-features
docs/oss/api-reference` returns no live/example references (only
migration/deprecation guidance remains in upgrade-notes and deprecation
docs, as intended)
- [x] Prettier formatting passes on edited files
- [x] pre-commit hooks (trailing newlines, markdown link check,
prettier) all pass
- [ ] Reviewer: visually scan the updated sections in `streaming-ssr.md`
and `turbolinks.md` to confirm the reframing reads well

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs-cleanup Documentation cleanup or migration P3 Parked priority review-needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remove immediate_hydration feature from everywhere at the codebase

2 participants