Skip to content

feat: align renderer cache staging with bundle-hash layout#3124

Open
justin808 wants to merge 14 commits intomainfrom
jg/3122-preseed-renderer-cache
Open

feat: align renderer cache staging with bundle-hash layout#3124
justin808 wants to merge 14 commits intomainfrom
jg/3122-preseed-renderer-cache

Conversation

@justin808
Copy link
Copy Markdown
Member

@justin808 justin808 commented Apr 12, 2026

Summary

  • positions react_on_rails_pro:pre_seed_renderer_cache as the preferred copy-based path for Docker/image builds, using the renderer's existing bundle-hash cache layout (<cache>/<bundleHash>/<bundleHash>.js)
  • updates the legacy react_on_rails_pro:pre_stage_bundle_for_node_renderer task to stage that same bundle-hash layout via symlinks for same-filesystem workflows such as local development, Heroku-style deploys, and bundle-caching restores
  • clarifies the docs so we describe long-standing runtime bundle uploads separately from build-time pre-seeding, and frame rolling deploys as seeding the current and previous bundle hashes rather than just copying a single bundle file
  • keeps RENDERER_SERVER_BUNDLE_CACHE_PATH as the preferred configuration knob, with RENDERER_BUNDLE_PATH documented as the deprecated compatibility alias

Closes #3122

Test plan

  • RUBYOPT='-rostruct -rrb-readline' bundle exec rspec spec/prepare_node_renderer_bundles_spec.rb spec/pre_seed_renderer_cache_spec.rb in react_on_rails_pro/spec/dummy
  • bundle exec rubocop react_on_rails_pro/lib/react_on_rails_pro/pre_seed_renderer_cache.rb react_on_rails_pro/lib/react_on_rails_pro/prepare_node_renderer_bundles.rb react_on_rails_pro/lib/tasks/assets.rake react_on_rails_pro/spec/dummy/spec/pre_seed_renderer_cache_spec.rb react_on_rails_pro/spec/dummy/spec/prepare_node_renderer_bundles_spec.rb
  • pnpm run lint
  • pnpm start format.listDifferent

Notes

  • The focused dummy-app spec run in this workspace required RUBYOPT='-rostruct -rrb-readline' because the local Ruby is 4.0.1 and the dummy app still pulls in older pry/pry-doc expectations.
  • Running bundle install in the dummy app on Ruby 4.0.1 tries to refresh react_on_rails_pro/spec/dummy/Gemfile.lock; that incidental lockfile diff was intentionally restored out of this branch after validation.

Note

Medium Risk
Moderate risk because it changes how Pro stages/locates Node Renderer SSR bundles (new bundle-hash directory layout, new rake task, and env-var precedence), which can affect production deploy/startup behavior if misconfigured.

Overview
Adds a new Pro rake task react_on_rails_pro:pre_seed_renderer_cache that copies compiled SSR (and optional RSC) bundles plus required assets into the Node Renderer’s bundle-hash cache layout during Docker/image builds to avoid first-request 410→retry latency.

Updates the legacy react_on_rails_pro:pre_stage_bundle_for_node_renderer flow to symlink into the same <cache>/<bundleHash>/<bundleHash>.js layout, centralizes staging logic in RendererCacheHelpers (including friendlier missing-bundle/RSC-manifest errors), and introduces RENDERER_SERVER_BUNDLE_CACHE_PATH as the preferred cache directory knob with RENDERER_BUNDLE_PATH kept as a deprecated alias (one-time warning). Docs/CHANGELOG and dummy-app specs are updated accordingly.

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

Summary by CodeRabbit

  • New Features

    • Rake task to pre-seed the Node Renderer bundle cache for Docker/image builds, including RSC bundles
    • Option to configure the renderer cache directory via a new environment variable
  • Deprecations

    • Legacy cache env var is deprecated and now emits a warning when used
  • Documentation

    • Deployment guides updated for Docker, Heroku, and bundle-caching workflows
  • Tests

    • New and updated specs covering pre-seeding, symlinked staging, RSC support, and env-driven cache selection

Adds `react_on_rails_pro:pre_seed_renderer_cache` rake task that copies
compiled server bundles into the Node Renderer's cache directory structure
during Docker image builds. This eliminates the 410→retry cold-start
latency (200ms–1s+) on the first SSR request after deployment.

Unlike the existing `pre_stage_bundle_for_node_renderer` (which creates
symlinks for local/CI use), this task copies files into the subdirectory
structure the renderer expects: <cache>/<bundleHash>/<bundleHash>.js

Supports RENDERER_SERVER_BUNDLE_CACHE_PATH env var, RSC bundles, and
assets_to_copy configuration.

Closes #3122

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 12, 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

Adds a Docker/CI-oriented rake task and supporting utilities to pre-seed the React On Rails Pro Node Renderer on-disk bundle cache by copying compiled server (and optional RSC) bundles plus configured assets into per-hash cache directories; includes cache-path resolution with a deprecated env-var fallback, docs, and tests.

Changes

Cohort / File(s) Summary
Documentation
docs/pro/node-renderer.md, docs/oss/building-features/bundle-caching.md, docs/oss/building-features/node-renderer/basics.md, docs/oss/building-features/node-renderer/heroku.md
Added/updated guidance for Docker/image-build cache pre-seeding, ENV precedence (RENDERER_SERVER_BUNDLE_CACHE_PATH preferred; RENDERER_BUNDLE_PATH deprecated), RSC bundle handling, and rolling-deploy notes.
Public API bootstrap
react_on_rails_pro/lib/react_on_rails_pro.rb
Require the new pre-seed implementation so it loads with the Pro library.
Pre-seed implementation
react_on_rails_pro/lib/react_on_rails_pro/pre_seed_renderer_cache.rb
New ReactOnRailsPro::PreSeedRendererCache class: resolves cache dir, validates bundles, copies server and (optionally) RSC bundles into <cache_dir>/<bundle_hash>/<bundle_hash>.js, and copies configured assets; emits warnings/errors for missing files.
Prepare task refactor
react_on_rails_pro/lib/react_on_rails_pro/prepare_node_renderer_bundles.rb
Refactored staging to create per-hash bundle directories and symlink bundle and assets into <cache_dir>/<bundle_hash>/; stricter RSC asset validation and improved symlink canonicalization; added helper methods.
Utilities
react_on_rails_pro/lib/react_on_rails_pro/utils.rb
Added resolve_renderer_cache_dir (prefers RENDERER_SERVER_BUNDLE_CACHE_PATH, falls back to deprecated RENDERER_BUNDLE_PATH with one-time deprecation warning, then Rails default) and reset_renderer_bundle_path_deprecation_warned!.
Rake tasks
react_on_rails_pro/lib/tasks/assets.rake
Added react_on_rails_pro:pre_seed_renderer_cache task (calls PreSeedRendererCache.call) and updated description for legacy pre_stage_bundle_for_node_renderer task.
Tests
react_on_rails_pro/spec/dummy/spec/pre_seed_renderer_cache_spec.rb, react_on_rails_pro/spec/dummy/spec/prepare_node_renderer_bundles_spec.rb
New/updated RSpec coverage validating copy-based pre-seed behavior, symlink staging behavior, asset handling, RSC coverage, ENV selection and deprecation warning behavior, and error cases.
Changelog
CHANGELOG.md
Added [Pro] Unreleased → Added entry documenting react_on_rails_pro:pre_seed_renderer_cache, RSC notes, and deprecation of RENDERER_BUNDLE_PATH in favor of RENDERER_SERVER_BUNDLE_CACHE_PATH.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Builder as Docker Build / CI
  participant Image as Docker Image FS
  participant Container as App Container (startup)
  participant Renderer as Node Renderer
  participant Rails as Rails App

  Builder->>Image: copy compiled server bundle & assets into\n<cache_dir>/<bundle_hash>/<bundle_hash>.js
  Note right of Image: optionally copy previous bundle hash from artifact store
  Image->>Container: container created with pre-seeded cache
  Container->>Renderer: renderer reads cache dir (<cache_dir>/<bundle_hash>/...)
  Renderer-->>Container: bundle available (no 410)
  Rails->>Renderer: SSR request referencing bundle_hash
  Renderer-->>Rails: rendered HTML (fast)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 I hopped through Docker, small and spry,
I copied bundles so cold starts fly,
I tucked hashes in neat little rows,
First SSR greets you without any woes,
Hooray — no 410s, onward we go! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 'feat: align renderer cache staging with bundle-hash layout' directly describes the main change—aligning cache staging with a bundle-hash directory structure across both pre-seed (copy) and pre-stage (symlink) workflows.
Linked Issues check ✅ Passed All coding requirements from issue #3122 are met: new react_on_rails_pro:pre_seed_renderer_cache task (copy-based), refactored pre_stage_bundle_for_node_renderer task (symlink-based), bundle-hash cache layout, asset copying, RSC bundle support, centralized cache-dir resolution, RENDERER_SERVER_BUNDLE_CACHE_PATH preference with RENDERER_BUNDLE_PATH deprecation, and comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are directly scoped to #3122: renderer cache pre-seeding logic, task definitions, utility functions, documentation, and tests. No unrelated refactoring, dependency updates, or out-of-scope modifications are present.
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/3122-preseed-renderer-cache

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
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 553d4c5ef5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread react_on_rails_pro/lib/react_on_rails_pro/pre_seed_renderer_cache.rb Outdated
Comment thread CHANGELOG.md Outdated
Comment thread react_on_rails_pro/lib/react_on_rails_pro/pre_seed_renderer_cache.rb Outdated
Comment thread react_on_rails_pro/lib/react_on_rails_pro/pre_seed_renderer_cache.rb Outdated
Comment thread react_on_rails_pro/spec/dummy/spec/pre_seed_renderer_cache_spec.rb
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 12, 2026

Review Summary

Overall the approach is clean and well-motivated — copying bundles at image-build time to avoid the 410→retry cold-start round-trip is a solid pattern. A few issues to address:

Must fix

  • CHANGELOG PR number — entry links to PR 3123 but this is 3124 (inline suggestion posted).

Should fix

  • RENDERER_BUNDLE_PATH deprecation inconsistencyPrepareNodeRenderBundles (the pre_stage_bundle_for_node_renderer task) still reads RENDERER_BUNDLE_PATH on line 20 without any warning. Deprecating it in the new task but not the existing one will confuse users running both. Either start the deprecation cycle in both places, or hold off on the warning here until you're ready to deprecate across the board. (Inline comment posted.)

  • copy_assets depends on implicit directory creation — the method works only because seed_bundle happens to run first and creates the directory. A single FileUtils.mkdir_p(bundle_dir) at the top of copy_assets makes the method self-contained and safe to call in isolation. (Inline suggestion posted.)

Nice to have

  • Missing RSC test coverage — the enable_rsc_support: true branch (lines 29–34 of pre_seed_renderer_cache.rb) is not exercised by any spec. A test verifying that both server and RSC bundles land in their respective subdirectories would round out coverage. (Inline comment with example posted.)

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 12, 2026

Greptile Summary

Adds the react_on_rails_pro:pre_seed_renderer_cache rake task and a supporting PreSeedRendererCache class that copies compiled server bundles (and optional assets/RSC manifests) into the Node Renderer's subdirectory cache structure (<cache>/<bundleHash>/<bundleHash>.js) during Docker image builds, eliminating the 410→retry cold-start penalty on first SSR request. All remaining findings are P2 (wrong PR number in CHANGELOG, missing explicit require "fileutils", no RSC code-path test).

Confidence Score: 5/5

Safe to merge — the implementation is correct, consistent with existing conventions, and all P2 findings are minor style/documentation improvements.

No P0 or P1 issues found. The three findings are all P2: a wrong PR link in the changelog, a missing explicit require "fileutils", and an untested RSC code path. None of these affect runtime correctness.

CHANGELOG.md (wrong PR number), react_on_rails_pro/lib/react_on_rails_pro/pre_seed_renderer_cache.rb (missing require), react_on_rails_pro/spec/dummy/spec/pre_seed_renderer_cache_spec.rb (no RSC test)

Important Files Changed

Filename Overview
react_on_rails_pro/lib/react_on_rails_pro/pre_seed_renderer_cache.rb New class implementing Docker-optimized bundle pre-seeding via file copies; logic is sound but missing explicit require "fileutils" and RSC path lacks test coverage.
react_on_rails_pro/lib/tasks/assets.rake Adds pre_seed_renderer_cache rake task delegating to PreSeedRendererCache.call; straightforward and consistent with existing tasks.
react_on_rails_pro/spec/dummy/spec/pre_seed_renderer_cache_spec.rb Good coverage of bundle copy, missing-asset warning, missing-bundle error, and env var paths; RSC enabled code path is not tested.
react_on_rails_pro/lib/react_on_rails_pro.rb Adds require for the new class in the correct location alongside related files.
docs/pro/node-renderer.md Clear new section documenting the pre-seeding flow, env var precedence, Dockerfile snippet, and rolling-deploy tip; no issues.
CHANGELOG.md Changelog entry is accurate except the PR link references #3123 instead of the correct #3124.

Sequence Diagram

sequenceDiagram
    participant D as Docker Build
    participant Rake as rake pre_seed_renderer_cache
    participant PSR as PreSeedRendererCache
    participant NRP as NodeRenderingPool
    participant FS as Filesystem

    D->>Rake: bundle exec rake react_on_rails_pro:pre_seed_renderer_cache
    Rake->>PSR: PreSeedRendererCache.call
    PSR->>PSR: resolve_cache_dir (ENV or Rails.root default)
    PSR->>NRP: server_bundle_hash
    NRP-->>PSR: "abc123.js"
    PSR->>FS: mkdir_p cache/abc123.js/
    PSR->>FS: cp server-bundle.js → cache/abc123.js/abc123.js.js
    PSR->>PSR: collect_assets (assets_to_copy + RSC manifests if RSC enabled)
    PSR->>FS: cp each asset → cache/abc123.js/
    alt RSC support enabled
        PSR->>NRP: rsc_bundle_hash
        NRP-->>PSR: "rsc-xyz789.js"
        PSR->>FS: mkdir_p cache/rsc-xyz789.js/
        PSR->>FS: cp rsc-bundle.js → cache/rsc-xyz789.js/rsc-xyz789.js.js
        PSR->>FS: cp each asset → cache/rsc-xyz789.js/
    end
    D->>D: Container starts — Node Renderer finds pre-seeded cache on startup
Loading

Reviews (1): Last reviewed commit: "feat: add pre_seed_renderer_cache rake t..." | Re-trigger Greptile

Comment thread CHANGELOG.md Outdated
Comment thread react_on_rails_pro/spec/dummy/spec/pre_seed_renderer_cache_spec.rb
- Fix wrong PR number in CHANGELOG (3123→3124)
- Guard missing bundle before calling server_bundle_hash to prevent
  Errno::ENOENT from masking the user-friendly error message
- Add require "fileutils" for explicit dependency
- Add RENDERER_BUNDLE_PATH deprecation warning to PrepareNodeRenderBundles
  for consistency, and support RENDERER_SERVER_BUNDLE_CACHE_PATH there too
- Add RSC test coverage (enable_rsc_support: true) verifying both server
  and RSC bundles plus manifest assets land in correct subdirectories

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@justin808
Copy link
Copy Markdown
Member Author

Review Feedback Addressed (e18004c)

All review comments have been addressed and threads resolved:

Fixed (5 items):

  1. CHANGELOG PR number — corrected 3123→3124
  2. Bundle existence guard — added validate_bundle_exists! before calling server_bundle_hash/rsc_bundle_hash, preventing Errno::ENOENT from masking the user-friendly error
  3. Deprecation consistency — added RENDERER_BUNDLE_PATH deprecation warning to PrepareNodeRenderBundles and support for RENDERER_SERVER_BUNDLE_CACHE_PATH there too
  4. Explicit require "fileutils" — added to match the pattern in prepare_node_renderer_bundles.rb
  5. RSC test coverage — two new specs verify both server and RSC bundles plus manifest assets land in correct subdirectories

Declined (1 item):

  • copy_assets defensive mkdir_p — ordering is guaranteed by the private method call structure in self.call

All 8 tests pass locally. Rationale replies posted on duplicate/declined items.

Comment thread react_on_rails_pro/lib/react_on_rails_pro/pre_seed_renderer_cache.rb Outdated
Comment thread react_on_rails_pro/spec/dummy/spec/pre_seed_renderer_cache_spec.rb
Comment thread react_on_rails_pro/spec/dummy/spec/pre_seed_renderer_cache_spec.rb
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 13, 2026

Review: pre_seed_renderer_cache rake task

Good feature — pre-seeding the Node Renderer cache at Docker build time is a clean way to eliminate the cold-start 410→retry latency. The implementation is straightforward and the tests cover the key paths. A few issues to address before merging:

Bug (must fix)

PrepareNodeRenderBundles#resolve_dest_path emits a spurious deprecation warning when both RENDERER_SERVER_BUNDLE_CACHE_PATH and RENDERER_BUNDLE_PATH are set. The warning fires on any presence of RENDERER_BUNDLE_PATH, even when the new var takes precedence and the deprecated one is never used. The new PreSeedRendererCache#resolve_cache_dir gets this right (the elsif branch only warns when the deprecated var is actually selected). See inline comment on prepare_node_renderer_bundles.rb.

Design concern

The env-var resolver is duplicated. Both resolve_cache_dir and resolve_dest_path implement the same three-way precedence. If the logic or deprecation message changes, both must be updated. Extracting a shared ReactOnRailsPro::RendererCachePath.resolve helper would eliminate the duplication and let the deprecation be tested once. See inline comment on pre_seed_renderer_cache.rb.

Test gaps

  1. Missing coverage for the "both env vars set" case — the most important invariant (preferred var wins, no deprecation noise) is untested. This is also the scenario the bug above is about.
  2. Test teardown leaks the server bundle filebefore creates public/webpack/production/server-bundle.js but after only removes cache_dir. The bundle file accumulates between spec runs. See inline comments.

Minor observations

  • seed_bundle and copy_assets both derive bundle_dir independently. If bundle_hash were ever nil, both would silently produce garbage paths. Not a realistic risk given calc_bundle_hash's implementation, but a defensive check in seed_bundle (raise on blank hash) would make failures loud rather than silent.
  • The docs section on rolling deploys ("Pre-seeding the previous bundle") is good context but could mention the expected directory layout (<cache>/<hash>/<hash>.js) so it's clear what the artifact store download should produce.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e18004c70a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread react_on_rails_pro/lib/react_on_rails_pro/pre_seed_renderer_cache.rb Outdated
… warning

Moves the renderer cache directory resolution logic (env var priority +
deprecation warning) into Utils.resolve_renderer_cache_dir. Both
PrepareNodeRenderBundles and PreSeedRendererCache now delegate to it,
eliminating duplication and fixing the bug where resolve_dest_path warned
about RENDERER_BUNDLE_PATH even when RENDERER_SERVER_BUNDLE_CACHE_PATH
was set and being used.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@justin808
Copy link
Copy Markdown
Member Author

Review Feedback Addressed (ab3262f)

Fixed (2 items):

  1. Spurious deprecation warning (@cursor[bot]) — resolve_dest_path in PrepareNodeRenderBundles warned about RENDERER_BUNDLE_PATH even when RENDERER_SERVER_BUNDLE_CACHE_PATH was set and being used. Fixed by extracting the shared logic into Utils.resolve_renderer_cache_dir with the correct if/elsif/else structure.

  2. Duplicated cache dir resolution (@cursor[bot]) — resolve_dest_path and resolve_cache_dir implemented the same env-var-priority logic in two separate classes. Both now delegate to ReactOnRailsPro::Utils.resolve_renderer_cache_dir, eliminating duplication and preventing future drift.

All 8 pre_seed_renderer_cache specs pass. All review threads resolved.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ab3262fdf4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread react_on_rails_pro/lib/react_on_rails_pro/utils.rb
…cache

Add test verifying preferred env var wins when both
RENDERER_SERVER_BUNDLE_CACHE_PATH and RENDERER_BUNDLE_PATH are set,
with no spurious deprecation warning. Fix after block to clean up
server_bundle_path test artifact.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread react_on_rails_pro/lib/react_on_rails_pro/pre_seed_renderer_cache.rb Outdated
Comment thread react_on_rails_pro/lib/react_on_rails_pro/pre_seed_renderer_cache.rb Outdated
Comment thread react_on_rails_pro/spec/dummy/spec/pre_seed_renderer_cache_spec.rb
Comment thread react_on_rails_pro/lib/react_on_rails_pro/utils.rb
@justin808
Copy link
Copy Markdown
Member Author

Review Feedback Addressed (9f79213)

Fixed (2 items):

  1. Missing regression test for both env vars set (@claude[bot]) — added test verifying RENDERER_SERVER_BUNDLE_CACHE_PATH takes precedence when both env vars are present, with no spurious deprecation warning emitted.

  2. Test teardown leaking server bundle file (@claude[bot]) — added FileUtils.rm_f(server_bundle_path) to the top-level after block so the test artifact in public/webpack/production/ is cleaned up.

Skipped with rationale (3 items):

  1. Spurious deprecation warning fix (@claude[bot]) — duplicate of cursor[bot] comment already fixed in ab3262f.
  2. Duplicated resolver logic extraction (@claude[bot]) — duplicate of cursor[bot] comment already fixed in ab3262f.
  3. Default cache path mismatch with Node renderer /tmp fallback (@chatgpt-codex-connector[bot]) — not applicable in standard Docker deployments; docs already recommend setting the env var explicitly.

All 9 tests pass. All review threads replied to and resolved.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 13, 2026

Review

Clean, well-motivated feature — pre-seeding the renderer cache at image-build time to eliminate 410→retry cold-start latency is a solid pattern. RSC support, the deprecation path, and the test coverage (including the RSC-enabled paths) all look good. Four things to address before merge:

Should fix

  1. copy_assets is not self-contained (inline suggestion on lines 75–86 of pre_seed_renderer_cache.rb): the method works only because seed_bundle creates bundle_dir first. A single FileUtils.mkdir_p(bundle_dir) at the top makes it safe to call independently and removes the implicit ordering dependency.

  2. Silent asset overwrite (inline on line 82): File.basename strips directory prefixes, so two assets_to_copy entries with the same filename silently collide. A warn when dest already exists would surface this immediately.

  3. Repeated deprecation warning (inline on utils.rb lines 182–192): resolve_renderer_cache_dir is called every time either rake task runs, and in test suites with RENDERER_BUNDLE_PATH set you'll get a wall of identical warnings. A per-process guard (@renderer_bundle_path_deprecated_warned ||= …) keeps it actionable.

Nice to have

  1. Spec placement comment (inline on spec line 4): the # rubocop:disable RSpec/FilePath is silencing a valid signal. The test needs the dummy Rails environment (Rails.root, filesystem helpers), so the location is justified — a one-line comment saying so prevents a future contributor from "fixing" the path and breaking the tests.

Comment thread react_on_rails_pro/lib/react_on_rails_pro/pre_seed_renderer_cache.rb Outdated
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

🤖 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_pro/lib/react_on_rails_pro/prepare_node_renderer_bundles.rb`:
- Around line 3-4: The file uses FileUtils (see extend FileUtils and subsequent
FileUtils method calls in PrepareNodeRendererBundles) but never requires it; add
require "fileutils" at the top of
react_on_rails_pro/lib/react_on_rails_pro/prepare_node_renderer_bundles.rb so
the FileUtils constant is always available before using extend FileUtils and any
FileUtils.* calls.
🪄 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: 342b7d49-a244-4346-92ef-ae50c7cffe97

📥 Commits

Reviewing files that changed from the base of the PR and between ad3e8d6 and 6aa470b.

📒 Files selected for processing (4)
  • react_on_rails_pro/lib/react_on_rails_pro/pre_seed_renderer_cache.rb
  • react_on_rails_pro/lib/react_on_rails_pro/prepare_node_renderer_bundles.rb
  • react_on_rails_pro/lib/react_on_rails_pro/utils.rb
  • react_on_rails_pro/spec/dummy/spec/prepare_node_renderer_bundles_spec.rb
🚧 Files skipped from review as they are similar to previous changes (2)
  • react_on_rails_pro/spec/dummy/spec/prepare_node_renderer_bundles_spec.rb
  • react_on_rails_pro/lib/react_on_rails_pro/utils.rb

Comment thread react_on_rails_pro/lib/react_on_rails_pro/prepare_node_renderer_bundles.rb Outdated
…ion guard

Addresses review feedback on PR #3124:

- Add missing `require "fileutils"` to prepare_node_renderer_bundles.rb
  so FileUtils is never resolved via implicit loading.
- Extract duplicated helpers (collect_assets, required_rsc_asset_paths,
  validate_bundle_exists!, bundle_sources) into RendererCacheHelpers.
  PreSeedRendererCache and PrepareNodeRenderBundles now delegate there,
  keeping only their copy-vs-symlink distinction local.
- bundle_sources now resolves hash values at construction time so the
  caller iterates (path, hash) pairs directly — no more indirect
  public_send on method-name symbols.
- Wrap the RENDERER_BUNDLE_PATH deprecation guard in a Mutex so the
  one-time warning holds under multi-threaded callers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@justin808
Copy link
Copy Markdown
Member Author

Review feedback addressed — 89d1d52

Thanks everyone for the thorough reviews. I (Claude Code) worked through all 16 unresolved threads — below is the per-category summary.

Fixed in 89d1d52

  • require "fileutils" missing (@coderabbitai) — added explicit require so FileUtils isn't resolved via implicit loading.
  • Indirect public_send on method-name symbols (@claude) — bundle_sources now returns resolved [path, hash] pairs; callers destructure directly.
  • Duplication between PreSeedRendererCache and PrepareNodeRenderBundles (@claude, @cursor) — extracted collect_assets, required_rsc_asset_paths, validate_bundle_exists!, and bundle_sources into a new ReactOnRailsPro::RendererCacheHelpers module. Copy-vs-symlink is the only leaf distinction.
  • Thread-safety gap on deprecation-warning guard (@claude) — wrapped the read/write of the guard in a private Mutex; the reset_! test helper also takes the lock.

While refactoring I also fully hoisted required_rsc_asset_paths out of the bundle_sources loop in both classes (previously only hoisted out of the inner assets loop), closing another hot-loop allocation noted earlier.

Already addressed in earlier commits on this branch

Ten threads were flagged against older commits but the code had already moved on:

  • Spec cleanup for loadable-stats* assets — outer after already removes both.
  • compact_blank in both classes — already in place.
  • collect_assets hoist above the loop — already above.
  • [ReactOnRailsPro] warning prefix consistency — already present.
  • :nodoc: annotation on deprecation-guard reset method — already annotated.
  • make_relative_symlink private_class_method — already private.
  • Match required RSC assets by full expanded path, not basename — already using File.expand_path-keyed Set.
  • Test helper reset_…_warned! exposure — kept as public :nodoc: method intentionally, since resolve_renderer_cache_dir is invoked during engine boot in addition to the rake task, and always-warn would be noisy.

All 16 threads have been replied to inline and resolved. Green on:

  • bundle exec rubocop on all four modified files
  • spec/dummy/spec/prepare_node_renderer_bundles_spec.rb + spec/dummy/spec/pre_seed_renderer_cache_spec.rb — 18 examples, 0 failures
  • spec/react_on_rails_pro/utils_spec.rb — 31 examples, 0 failures

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 18, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Let me take a look at the new RendererCacheHelpers module and the updated files before responding.


🏁 Script executed:

#!/bin/bash
fd "renderer_cache_helpers" --type f
fd "utils.rb" react_on_rails_pro/lib --type f
cat react_on_rails_pro/lib/react_on_rails_pro/utils.rb
echo "---"
fd "renderer_cache_helpers.rb" --type f | xargs cat 2>/dev/null || echo "File not found via fd"
echo "---"
cat react_on_rails_pro/lib/react_on_rails_pro/pre_seed_renderer_cache.rb
echo "---"
cat react_on_rails_pro/lib/react_on_rails_pro/prepare_node_renderer_bundles.rb

Length of output: 18151


@justin808 Excellent work on this final refactoring round — the code is in great shape. A few highlights after reviewing the actual files:

RendererCacheHelpers module — The module_function approach is exactly right here: both PreSeedRendererCache and PrepareNodeRenderBundles call the helpers directly on the module namespace without needing to include/extend, keeping the callers clean. The Set return from required_rsc_asset_paths (including Set.new when RSC is disabled) is a nice touch — O(1) include? lookups and no nil-guard needed at call sites.

Thread-safe deprecation guardRENDERER_BUNDLE_PATH_DEPRECATION_MUTEX with private_constant is the correct pattern. Both the warn path and the reset_! helper are inside synchronize, so there's no TOCTOU race between checking @renderer_bundle_path_deprecation_warned and setting it.

bundle_sources returning [path, hash] pairs — Eliminates the public_send indirection cleanly; callers destructure directly in the each block, which reads well.

Copy-vs-symlink as the only leaf distinction — The shared collect_assets, required_rsc_asset_paths, validate_bundle_exists!, and bundle_sources in the helper module mean the two classes differ only in FileUtils.cp vs make_relative_symlink. That's exactly the right level of abstraction.

18 + 31 examples all green with RuboCop clean — happy to approve. 🐇

[approve]


🧠 Learnings used
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 2464
File: packages/react-on-rails-pro-node-renderer/src/worker.ts:343-393
Timestamp: 2026-02-21T20:46:38.404Z
Learning: In packages/react-on-rails-pro-node-renderer, the `Promise.all` pattern for per-bundle operations (e.g., in `/upload-assets` endpoint and `handleNewBundlesProvided` in handleRenderRequest.ts) is intentional. Copy operations like `copyUploadedAssets` are idempotent and retries are safe, so fast-fail behavior on lock acquisition or copy errors is acceptable. Switching to `Promise.allSettled` to surface partial success would require Ruby-side protocol changes.

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 0
File: :0-0
Timestamp: 2026-04-13T02:29:20.920Z
Learning: In `react_on_rails_pro/lib/react_on_rails_pro/engine.rb` and `react_on_rails/lib/react_on_rails/configuration.rb` (PR `#3094`): Do NOT reset `rendering_strategy` or `js_code_builder` inside `ReactOnRails.configure`. The Pro engine sets these exactly once during `after_initialize`; resetting them on reconfigure would silently drop Pro's NodeStrategy/ProJsCodeBuilder assignments. A proper reconfigure mechanism for these accessors is deferred to Step 3 of the strategy-pattern refactor (`#2905`).

Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: react_on_rails_pro/CLAUDE.md:0-0
Timestamp: 2026-02-24T16:52:47.694Z
Learning: Applies to react_on_rails_pro/spec/dummy/commonWebpackConfig.js : Changes to `commonWebpackConfig.js` affect all three webpack bundles (client, server, and RSC) in the Pro dummy app

Learnt from: ihabadham
Repo: shakacode/react_on_rails PR: 2284
File: react_on_rails/lib/generators/react_on_rails/templates/pro/base/config/initializers/react_on_rails_pro.rb.tt:10-10
Timestamp: 2026-02-12T20:43:26.843Z
Learning: In React on Rails Pro initializer templates (e.g., `react_on_rails_pro.rb.tt`), the `config.renderer_password` uses `ENV.fetch("RENDERER_PASSWORD", "devPassword")` as a development starter configuration. This pattern is intentional: the node renderer requires explicit production deployment setup, and adding a production-only raise would break generator execution since initializers are loaded during `rails generate`. The pattern is consistent with existing manual setup documentation.

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 3094
File: react_on_rails_pro/lib/react_on_rails_pro/js_code_builder.rb:40-64
Timestamp: 2026-04-13T02:28:35.099Z
Learning: In `react_on_rails_pro/lib/react_on_rails_pro/js_code_builder.rb`, the `typeof generateRSCPayload !== 'function'` guards in `rsc_payload_function_section` are an intentional faithful port of the existing pattern in `server_rendering_js_code.rb` (lines 21 and 39). Do not flag this guard as a bug in this file; any fix should be tracked as a separate issue covering both files.

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 2534
File: packages/react-on-rails-pro-node-renderer/src/worker.ts:0-0
Timestamp: 2026-03-05T08:37:17.126Z
Learning: In `packages/react-on-rails-pro-node-renderer/src/worker.ts`, the `/upload-assets` endpoint intentionally returns 200 even when the bundle file (bundleFilePath) does not yet exist. This supports asset-only pre-upload workflows where assets are uploaded before the bundle file arrives. The `.complete` marker (`markBundleComplete`) is only written when both conditions hold: the bundle directory was previously incomplete (`wasIncomplete === true`) AND the bundle file is present (`fileExistsAsync(bundleFilePath) === true`). Do not suggest failing or returning an error when the bundle file is absent in this endpoint.

Learnt from: ihabadham
Repo: shakacode/react_on_rails PR: 2674
File: react_on_rails/lib/react_on_rails/doctor.rb:2325-2345
Timestamp: 2026-03-19T16:13:07.375Z
Learning: In `react_on_rails/lib/react_on_rails/doctor.rb`, `check_rsc_payload_route` and `check_rsc_procfile_watcher` filter commented lines (`/^\s*#/`) before checking for the target string, consistent with the `config_has_async_loading_strategy?` pattern. The doctor avoids a full Rails boot for resilience and uses file-content scanning instead of actual Rails route inspection. Remaining accepted limitations are dynamic/engine-mounted routes and routes loaded via the `draw` macro (uncommon, since the generator places `rsc_payload_route` directly in `config/routes.rb`).

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 2850
File: docs/oss/api-reference/rails-view-rendering-from-inline-javascript.md:0-0
Timestamp: 2026-03-26T07:01:43.653Z
Learning: In `packages/react-on-rails/src/ClientRenderer.ts`, the duplicate-render guard (which skips rendering if the DOM node is already connected/mounted) applies only to the automatic page-load rendering path. The public `ReactOnRails.render(...)` API routes through `packages/react-on-rails/src/base/client.ts` and calls `reactHydrateOrRender(...)` directly, without any deduplication guard. Therefore, repeated calls to the public `ReactOnRails.render(...)` API on the same DOM node (e.g., `#root`) WILL invoke React again — they are NOT deduplicated. Documentation stating this behavior is correct and should not be flagged.

Learnt from: justin808
Repo: shakacode/react_on_rails PR: 3124
File: react_on_rails_pro/spec/dummy/spec/pre_seed_renderer_cache_spec.rb:6-6
Timestamp: 2026-04-13T22:09:06.405Z
Learning: In `react_on_rails_pro/spec/dummy/spec/` (and similar RSpec files), an inline `# rubocop:disable RSpec/FilePath,RSpec/SpecFilePathFormat` on the `describe` line is the accepted and standard pattern for file-level path suppression. Because these cops are triggered only by the `describe` call itself, a paired `# rubocop:enable` after the closing `end` is unnecessary and should NOT be flagged.

Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: react_on_rails_pro/CLAUDE.md:0-0
Timestamp: 2026-02-24T16:52:47.694Z
Learning: Applies to react_on_rails_pro/spec/dummy/spec/**/*.rb : Pro integration tests should be run with: `cd react_on_rails_pro/spec/dummy && bundle exec rspec spec/`

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

Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: react_on_rails_pro/CLAUDE.md:0-0
Timestamp: 2026-02-24T16:52:47.694Z
Learning: Applies to react_on_rails_pro/spec/react_on_rails_pro/**/*.rb : Pro gem unit tests should be run with: `cd react_on_rails_pro && bundle exec rspec spec/react_on_rails_pro/`

Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: react_on_rails_pro/CLAUDE.md:0-0
Timestamp: 2026-02-24T16:52:47.694Z
Learning: Applies to react_on_rails_pro/spec/execjs-compatible-dummy/spec/**/*.rb : ExecJS dummy tests should be run with: `cd react_on_rails_pro/spec/execjs-compatible-dummy && bundle exec rspec`

Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: react_on_rails_pro/CLAUDE.md:0-0
Timestamp: 2026-02-24T16:52:47.694Z
Learning: Applies to react_on_rails_pro/packages/react-on-rails-pro-node-renderer/**/*.test.{ts,tsx,js,jsx} : Node renderer tests should be run with: `pnpm --filter react-on-rails-pro-node-renderer run test`

Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-12T23:15:11.806Z
Learning: Applies to CHANGELOG.md : Changelog format: `[PR 1818](https://github.com/shakacode/react_on_rails/pull/1818) by [username](https://github.com/username)` (no hash before PR number)

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 18, 2026

Code Review

Overall this is well-structured work — clear separation of copy vs symlink semantics, good shared helper extraction, solid test coverage across the new scenarios, and the deprecation-warning guard is correctly thread-safe. A few things worth addressing before merge:


Bugs / Correctness

extend FileUtils is dead code in PrepareNodeRenderBundles

The class-level extend FileUtils (line 13 of prepare_node_renderer_bundles.rb) made FileUtils methods available as bare class-method calls (e.g., mkdir_p dest_path in the old implementation). The refactor replaced every bare call with an explicit FileUtils.xxx call, but the extend was never removed. It has no effect and should be deleted to avoid confusion.

File.expand_path without a base is fragile in Docker/CI

Both copy_assets (pre_seed_renderer_cache.rb:54) and symlink_assets (prepare_node_renderer_bundles.rb:56) call File.expand_path(asset_path.to_s) without a second argument, so relative paths resolve from Dir.pwd at call time. In a Docker build Dir.pwd is typically /app (or wherever the image entrypoint sets it), which may differ from Rails.root. Since assets_to_copy values are usually built from Rails path helpers (absolute), this works in practice — but a defensive File.expand_path(asset_path.to_s, Rails.root) would make the intent explicit and guard against edge-cases.


Minor Code Quality

collect_assets calls ReactOnRailsPro.configuration twice

def collect_assets
  assets = Array(ReactOnRailsPro.configuration.assets_to_copy).dup
  if ReactOnRailsPro.configuration.enable_rsc_support

Cache the configuration object in a local variable to avoid the double lookup and make it obvious both accesses read the same snapshot.

reset_renderer_bundle_path_deprecation_warned! is public API

The method is documented as test-only (:nodoc:), but it's public and callable from any production code. Consider extracting it to a Support or Testing module that's only loaded in test environments, or at minimum add a comment warning that calling it in production will re-surface the deprecation warning.


Missing Test Coverage

PrepareNodeRenderBundles spec lacks a deprecated-RENDERER_BUNDLE_PATH test

pre_seed_renderer_cache_spec.rb covers the deprecated env-var case (warning fires, correct path used, no warning when both vars set). The updated prepare_node_renderer_bundles_spec.rb does not. Since both tasks share the same resolve_renderer_cache_dir code path, this is low risk — but for symmetry and regression-safety it's worth adding.


Docs / Changelog

Both are clear and accurate. The rolling-deploy "seed current and previous bundle hashes" guidance is a useful addition that wasn't there before.

Comment thread react_on_rails_pro/lib/react_on_rails_pro/prepare_node_renderer_bundles.rb Outdated
Comment thread react_on_rails_pro/lib/react_on_rails_pro/pre_seed_renderer_cache.rb Outdated
Comment thread react_on_rails_pro/lib/react_on_rails_pro/renderer_cache_helpers.rb
Comment thread react_on_rails_pro/lib/react_on_rails_pro/prepare_node_renderer_bundles.rb Outdated
Comment thread react_on_rails_pro/lib/react_on_rails_pro/renderer_cache_helpers.rb
- Move validate_bundle_exists! inside RendererCacheHelpers.bundle_sources so
  the guard runs before pool.server_bundle_hash / pool.rsc_bundle_hash are
  invoked. Hash computation eventually calls Digest::MD5.file / File.mtime on
  the bundle path, which raises raw Errno::ENOENT if the file is missing —
  bypassing the friendly ReactOnRailsPro::Error. Drop the now-redundant
  validate_bundle_exists! calls in both callers. Add a regression spec that
  un-stubs server_bundle_hash so the real hash-computation path runs on a
  missing bundle and asserts ReactOnRailsPro::Error (not Errno::ENOENT).
- Expand asset paths against Rails.root rather than Dir.pwd in both
  required_rsc_asset_paths and the per-asset lookups in copy_assets /
  symlink_assets. This keeps the Set comparison consistent when the rake
  task runs from a different cwd (e.g. inside a Docker RUN step).
- Snapshot ReactOnRailsPro.configuration once at the top of collect_assets
  so both reads see the same value and the method makes one dispatch.
- Remove dead `extend FileUtils` in PrepareNodeRenderBundles (no longer
  used after the refactor to explicit FileUtils. prefixes).
- Add symmetric RENDERER_BUNDLE_PATH deprecation spec in
  prepare_node_renderer_bundles_spec.rb.

All 20 dummy specs pass; rubocop clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@justin808
Copy link
Copy Markdown
Member Author

Addressed review feedback in 3a4bcc5

Fixed (6 items):

  1. Bundle existence guard ordering (must-fix, @cursor[bot]) — moved validate_bundle_exists! inside RendererCacheHelpers.bundle_sources so it runs before pool.server_bundle_hash, which eventually calls Digest::MD5.file / File.mtime and would otherwise leak Errno::ENOENT. Added regression spec that un-stubs server_bundle_hash and asserts ReactOnRailsPro::Error.
  2. Dead extend FileUtils — removed from PrepareNodeRenderBundles.
  3. File.expand_path base (items 3+4) — now expanded against Rails.root in both the required-assets Set construction AND the per-asset lookups in copy_assets / symlink_assets. Kept symmetric so the Set comparison still hits when the rake task runs from a different cwd.
  4. collect_assets config snapshot — local variable for one dispatch and consistent reads.
  5. Symmetric RENDERER_BUNDLE_PATH spec — mirrored the deprecated env var test into prepare_node_renderer_bundles_spec.rb.

Declined with rationale (1 item):

  1. Utils.reset_renderer_bundle_path_deprecation_warned! visibility — the :nodoc: comment explicitly marks intent and the method is namespaced on Utils so it stays discoverable for test code that already touches Utils (e.g. for env var resolution and path handling). Relocating to a separate Testing support module would add a new namespace for a single method and break the locality that makes the test helper obvious to anyone reading utils.rb. Pattern matches precedent elsewhere in the Pro codebase (e.g. remote_bundle_cache_adapter utilities). Leaving as-is.

All 20 dummy specs pass; rubocop clean.

I (Claude Code) addressed the feedback on behalf of @justin808.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 18, 2026

Code Review

Overview: Clean implementation. RendererCacheHelpers extraction, thread-safe one-time deprecation warning, and pre-validation of bundle paths before hash-computation are all good design decisions. Test coverage is thorough.

Issue 1 - Duplicated asset validation logic: copy_assets in PreSeedRendererCache and symlink_assets in PrepareNodeRenderBundles are structurally identical (expand path, check existence, test rsc_required_paths, raise/warn, then perform the file operation). Only the final step differs. Since RendererCacheHelpers was introduced to eliminate this duplication, the per-asset validation belongs there too.

Issue 2 - Non-atomic copy in seed_bundle: FileUtils.cp writes directly to the destination. If killed mid-copy (OOM during Docker build), the cache is left with a partial bundleHash.js. A cp-to-tmp-then-mv pattern gives atomic promotion.

Issue 3 - Node Renderer binary compatibility: The Ruby side now prefers RENDERER_SERVER_BUNDLE_CACHE_PATH and deprecates RENDERER_BUNDLE_PATH, but the updated docs show starting the renderer binary with the new var. Has the Node Renderer binary been updated to read RENDERER_SERVER_BUNDLE_CACHE_PATH? If not, users who follow the updated docs will configure the Ruby rake task correctly but the renderer falls back to its own default, making the pre-seeded cache unreachable. Should be called out in migration notes if this requires a renderer version bump.

Issue 4 - compact_blank may silently discard valid paths: compact_blank (ActiveSupport) removes nil and blank? values including empty strings. compact would be safer — it only removes nil, and an empty/whitespace path would surface downstream as a warning rather than disappearing silently.

Minor: resolve_cache_dir in PreSeedRendererCache is a one-line private wrapper around Utils.resolve_renderer_cache_dir — inlining removes unnecessary indirection. Also: make_relative_symlink was previously public; confirming no external consumers (e.g. host-app custom asset tasks) depend on it would be worth a quick grep.

Comment thread react_on_rails_pro/lib/react_on_rails_pro/renderer_cache_helpers.rb Outdated
Comment thread react_on_rails_pro/lib/react_on_rails_pro/pre_seed_renderer_cache.rb Outdated
Comment thread react_on_rails_pro/lib/react_on_rails_pro/pre_seed_renderer_cache.rb Outdated
Comment thread react_on_rails_pro/lib/react_on_rails_pro/pre_seed_renderer_cache.rb Outdated
Comment thread docs/pro/node-renderer.md
justin808 added a commit that referenced this pull request Apr 18, 2026
…l(mode:)

Collapse the copy-based PreSeedRendererCache and the symlink-based
PrepareNodeRenderBundles into a single entry point with a `mode:` keyword
argument. Both modes produce the identical <cache>/<hash>/<hash>.js layout;
only the file operation differs (FileUtils.cp vs relative symlink).

Changes:

- ReactOnRailsPro::PreSeedRendererCache.call now accepts mode: :copy
  (default, for Docker/image builds) or mode: :symlink (same-filesystem
  workflows). Rejects unknown modes with ArgumentError.
- mode: :copy now raises a clear error when neither
  RENDERER_SERVER_BUNDLE_CACHE_PATH nor RENDERER_BUNDLE_PATH is set in
  non-dev/test environments. The Node renderer's default lookup can
  differ from the Ruby side (falling back to /tmp when cwd is outside
  the app tree), so silent fallback is unsafe for production-like
  deploys and was a silent-misconfig footgun.
- `react_on_rails_pro:pre_seed_renderer_cache` rake task accepts
  MODE=copy (default) or MODE=symlink.
- ReactOnRailsPro::PrepareNodeRenderBundles is now a thin deprecated
  shim that emits a once-per-process warning and delegates to
  PreSeedRendererCache.call(mode: :symlink). Public class and the
  `pre_stage_bundle_for_node_renderer` rake task remain for backward
  compatibility.
- AssetsPrecompile.call invokes the unified API with mode: :symlink
  after precompile (no behavior change for existing users).
- react_on_rails:doctor scans common deploy-script locations
  (Procfile*, Dockerfile, bin/*) for references to the deprecated
  pre_stage_bundle_for_node_renderer task and surfaces a migration
  warning.
- Docs: docs/pro/node-renderer.md describes the unified API, both
  modes, the non-dev env-var requirement for copy mode, and the
  deprecation story.
- CHANGELOG: new Changed entry describing the unification.
- Tests: mode validation, raise-in-non-dev guard (including symlink
  opt-out), deprecation warning on the shim, and a doctor check test.

Context: stacked on PR #3124 (jg/3122-preseed-renderer-cache).
Refs: #3122

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
justin808 added a commit that referenced this pull request Apr 19, 2026
…ion guard

Addresses review feedback on PR #3124:

- Add missing `require "fileutils"` to prepare_node_renderer_bundles.rb
  so FileUtils is never resolved via implicit loading.
- Extract duplicated helpers (collect_assets, required_rsc_asset_paths,
  validate_bundle_exists!, bundle_sources) into RendererCacheHelpers.
  PreSeedRendererCache and PrepareNodeRenderBundles now delegate there,
  keeping only their copy-vs-symlink distinction local.
- bundle_sources now resolves hash values at construction time so the
  caller iterates (path, hash) pairs directly — no more indirect
  public_send on method-name symbols.
- Wrap the RENDERER_BUNDLE_PATH deprecation guard in a Mutex so the
  one-time warning holds under multi-threaded callers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
justin808 added a commit that referenced this pull request Apr 19, 2026
…l(mode:)

Collapse the copy-based PreSeedRendererCache and the symlink-based
PrepareNodeRenderBundles into a single entry point with a `mode:` keyword
argument. Both modes produce the identical <cache>/<hash>/<hash>.js layout;
only the file operation differs (FileUtils.cp vs relative symlink).

Changes:

- ReactOnRailsPro::PreSeedRendererCache.call now accepts mode: :copy
  (default, for Docker/image builds) or mode: :symlink (same-filesystem
  workflows). Rejects unknown modes with ArgumentError.
- mode: :copy now raises a clear error when neither
  RENDERER_SERVER_BUNDLE_CACHE_PATH nor RENDERER_BUNDLE_PATH is set in
  non-dev/test environments. The Node renderer's default lookup can
  differ from the Ruby side (falling back to /tmp when cwd is outside
  the app tree), so silent fallback is unsafe for production-like
  deploys and was a silent-misconfig footgun.
- `react_on_rails_pro:pre_seed_renderer_cache` rake task accepts
  MODE=copy (default) or MODE=symlink.
- ReactOnRailsPro::PrepareNodeRenderBundles is now a thin deprecated
  shim that emits a once-per-process warning and delegates to
  PreSeedRendererCache.call(mode: :symlink). Public class and the
  `pre_stage_bundle_for_node_renderer` rake task remain for backward
  compatibility.
- AssetsPrecompile.call invokes the unified API with mode: :symlink
  after precompile (no behavior change for existing users).
- react_on_rails:doctor scans common deploy-script locations
  (Procfile*, Dockerfile, bin/*) for references to the deprecated
  pre_stage_bundle_for_node_renderer task and surfaces a migration
  warning.
- Docs: docs/pro/node-renderer.md describes the unified API, both
  modes, the non-dev env-var requirement for copy mode, and the
  deprecation story.
- CHANGELOG: new Changed entry describing the unification.
- Tests: mode validation, raise-in-non-dev guard (including symlink
  opt-out), deprecation warning on the shim, and a doctor check test.

Context: stacked on PR #3124 (jg/3122-preseed-renderer-cache).
Refs: #3122

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Duplicated asset validation logic across two classes
    • Extracted the shared asset expansion/validation/error-handling loop into RendererCacheHelpers.each_validated_asset and updated both copy and symlink flows to use it.

Create PR

Or push these changes by commenting:

@cursor push 86992fcd9f
Preview (86992fcd9f)
diff --git a/react_on_rails_pro/lib/react_on_rails_pro/pre_seed_renderer_cache.rb b/react_on_rails_pro/lib/react_on_rails_pro/pre_seed_renderer_cache.rb
--- a/react_on_rails_pro/lib/react_on_rails_pro/pre_seed_renderer_cache.rb
+++ b/react_on_rails_pro/lib/react_on_rails_pro/pre_seed_renderer_cache.rb
@@ -51,19 +51,8 @@
     # against Rails.root to match how RendererCacheHelpers.required_rsc_asset_paths
     # builds its Set.
     def self.copy_assets(assets, bundle_dir, rsc_required_paths)
-      assets.each do |asset_path|
-        expanded = File.expand_path(asset_path.to_s, Rails.root)
-        unless File.exist?(expanded)
-          if rsc_required_paths.include?(expanded)
-            raise ReactOnRailsPro::Error, "Required RSC asset not found: #{asset_path}. " \
-                                          "Build your bundles before pre-seeding the renderer cache."
-          end
-          warn "[ReactOnRailsPro] Asset not found #{asset_path}"
-          next
-        end
-
-        dest = File.join(bundle_dir, File.basename(expanded))
-        FileUtils.cp(expanded, dest)
+      RendererCacheHelpers.each_validated_asset(assets, bundle_dir, rsc_required_paths, "pre-seeding") do |source, dest|
+        FileUtils.cp(source, dest)
         puts "[ReactOnRailsPro] Copied asset: #{dest}"
       end
     end

diff --git a/react_on_rails_pro/lib/react_on_rails_pro/prepare_node_renderer_bundles.rb b/react_on_rails_pro/lib/react_on_rails_pro/prepare_node_renderer_bundles.rb
--- a/react_on_rails_pro/lib/react_on_rails_pro/prepare_node_renderer_bundles.rb
+++ b/react_on_rails_pro/lib/react_on_rails_pro/prepare_node_renderer_bundles.rb
@@ -45,23 +45,9 @@
       end
     end
 
-    # Required assets are matched by expanded path rather than basename so a
-    # same-named unrelated entry in assets_to_copy cannot trigger a false-
-    # positive "required" error. Expand against Rails.root to match how
-    # RendererCacheHelpers.required_rsc_asset_paths builds its Set.
     def self.symlink_assets(assets, bundle_dir, rsc_required_paths)
-      assets.each do |asset_path|
-        expanded = File.expand_path(asset_path.to_s, Rails.root)
-        unless File.exist?(expanded)
-          if rsc_required_paths.include?(expanded)
-            raise ReactOnRailsPro::Error, "Required RSC asset not found: #{asset_path}. " \
-                                          "Build your bundles before pre-staging the renderer cache."
-          end
-          warn "[ReactOnRailsPro] Asset not found #{asset_path}"
-          next
-        end
-
-        destination_full_path = File.join(bundle_dir, File.basename(expanded))
+      RendererCacheHelpers.each_validated_asset(assets, bundle_dir, rsc_required_paths, "pre-staging") do |expanded,
+                                                                                                            destination_full_path|
         make_relative_symlink(expanded, destination_full_path)
       end
     end

diff --git a/react_on_rails_pro/lib/react_on_rails_pro/renderer_cache_helpers.rb b/react_on_rails_pro/lib/react_on_rails_pro/renderer_cache_helpers.rb
--- a/react_on_rails_pro/lib/react_on_rails_pro/renderer_cache_helpers.rb
+++ b/react_on_rails_pro/lib/react_on_rails_pro/renderer_cache_helpers.rb
@@ -64,5 +64,22 @@
       sources << [rsc_bundle_path, pool.rsc_bundle_hash]
       sources
     end
+
+    def each_validated_asset(assets, bundle_dir, rsc_required_paths, action_description)
+      assets.each do |asset_path|
+        expanded = File.expand_path(asset_path.to_s, Rails.root)
+        unless File.exist?(expanded)
+          if rsc_required_paths.include?(expanded)
+            raise ReactOnRailsPro::Error, "Required RSC asset not found: #{asset_path}. " \
+                                          "Build your bundles before #{action_description} the renderer cache."
+          end
+          warn "[ReactOnRailsPro] Asset not found #{asset_path}"
+          next
+        end
+
+        destination_full_path = File.join(bundle_dir, File.basename(expanded))
+        yield expanded, destination_full_path
+      end
+    end
   end
 end

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit e120a09. Configure here.

@justin808
Copy link
Copy Markdown
Member Author

Addressed the remaining renderer-cache review threads on this base PR.

Fixed in the pushed stack:

  • asset path validation now expands against Rails.root through the shared helper
  • assets use compact instead of compact_blank so blank paths warn instead of disappearing
  • copy-mode bundle writes use copy-to-temp plus rename for atomic promotion
  • copy/symlink asset validation is centralized in RendererCacheHelpers
  • the one-line cache-dir wrapper was removed from this base branch
  • non-file asset paths are warned and skipped

Also verified the docs concern: the Node renderer reads RENDERER_SERVER_BUNDLE_CACHE_PATH in packages/react-on-rails-pro-node-renderer/src/shared/configBuilder.ts and the compiled lib/shared/configBuilder.js. The lower-stack fixes were propagated upward into #3167 and #3173.

@justin808
Copy link
Copy Markdown
Member Author

@claude review this PR

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.

Eliminate Node Renderer cold-start latency by pre-seeding bundle cache in Docker builds

1 participant