Add CacheSection component and section caching infrastructure (experimental)#4740
Add CacheSection component and section caching infrastructure (experimental)#4740AbanoubGhadban wants to merge 2 commits into
Conversation
…#4385) Demonstrates selective hydration architecture with: - Root component (SelectiveHydrationDemo.jsx) without "use client" directive - Four child client components with "use client" that hydrate independently: - SelectiveHydrationHeaderSection (menu toggle, search) - SelectiveHydrationArticleSection (like button, comments) - SelectiveHydrationCategoriesSection (category selection, newsletter) - SelectiveHydrationFooterSection (feedback form, contact modal) Each section is ~70vh tall to demonstrate below-the-fold content. Child components are in components/SelectiveHydration/ (not auto-loaded). Only the root component is in ror-auto-load-components/. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…app) Experimental implementation for investigating selective hydration caching: - Add CacheSection React component that wraps children in Suspense with configurable delay for time-based section division during streaming - Add selective_hydration_cached controller action using ActionController::Live to stream pre-cached HTML sections with configurable delays - Add section_cache rake task to capture streamed HTML into section files by time windows (no HTML parsing, pure timing-based division) - Update SelectiveHydrationDemo to use CacheSection for 4 sections - Replace CSP nonces dynamically when streaming cached content All changes are in react_on_rails_pro/spec/dummy (experimental). Usage: rake section_cache:generate[/selective_hydration_demo,4,5] curl http://localhost:5150/selective_hydration_cached?delay=1 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
WalkthroughAdds a selective-hydration demo with four independently delayed React sections, Rails Live streaming endpoints, generated section-cache tasks, cached HTML replay, and server-bundle registration updates. ChangesSelective hydration streaming
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Browser
participant RailsRoutes
participant PagesController
participant ReactOnRails
participant SelectiveHydrationDemo
participant CacheSection
Browser->>RailsRoutes: GET selective_hydration_demo
RailsRoutes->>PagesController: dispatch action
PagesController->>ReactOnRails: render streaming template
ReactOnRails->>SelectiveHydrationDemo: pass sectionDelays
SelectiveHydrationDemo->>CacheSection: render delayed sections
CacheSection-->>Browser: stream section HTML and hydration placeholders
Suggested labels: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Greptile SummaryThis PR adds an experimental selective-hydration section cache to the dummy app. The main changes are:
Confidence Score: 4/5The cache generation and replay paths can publish broken streams, and the public delay can exhaust server threads.
react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb and react_on_rails_pro/spec/dummy/lib/tasks/section_cache.rake
|
| Filename | Overview |
|---|---|
| react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb | Adds the cached streaming action, but its request-controlled delay is unbounded. |
| react_on_rails_pro/spec/dummy/lib/tasks/section_cache.rake | Adds cache management tasks with invalid-input, subprocess, timing, and atomic-publication bugs. |
| react_on_rails_pro/spec/dummy/client/app/components/CacheSection.jsx | Adds delayed server-side Suspense rendering for section content. |
| react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/SelectiveHydrationDemo.jsx | Composes four client sections under independently delayed cache boundaries. |
| react_on_rails_pro/spec/dummy/app/views/pages/selective_hydration_demo.html.erb | Adds the streaming demo view and forwards query-provided section delays. |
| react_on_rails_pro/spec/dummy/client/app/packs/server-bundle.js | Updates generated server-bundle loading for the server component registration path. |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Task as section_cache:generate
participant Demo as Demo endpoint
participant React as React stream
participant Disk as Section cache
participant Browser
participant Cached as Cached endpoint
Task->>Demo: GET with section delays
Demo->>React: Render Suspense boundaries
React-->>Task: Shell and reveal chunks
Task->>Disk: Bucket chunks by arrival time
Browser->>Cached: GET with replay delay
Cached->>Disk: Read section files
Disk-->>Cached: Cached HTML fragments
Cached-->>Browser: Stream fragments with current nonce
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Task as section_cache:generate
participant Demo as Demo endpoint
participant React as React stream
participant Disk as Section cache
participant Browser
participant Cached as Cached endpoint
Task->>Demo: GET with section delays
Demo->>React: Render Suspense boundaries
React-->>Task: Shell and reveal chunks
Task->>Disk: Bucket chunks by arrival time
Browser->>Cached: GET with replay delay
Cached->>Disk: Read section files
Disk-->>Cached: Cached HTML fragments
Cached-->>Browser: Stream fragments with current nonce
Reviews (1): Last reviewed commit: "Add CacheSection component and section c..." | Re-trigger Greptile
| def selective_hydration_cached # rubocop:disable Metrics/AbcSize | ||
| # Stream pre-cached section files with delays using ActionController::Live | ||
| # This simulates serving cached SSR content with progressive streaming | ||
| delay_seconds = (params[:delay] || 5).to_i |
There was a problem hiding this comment.
Unbounded Delay Exhausts Threads
The public delay parameter controls three blocking sleeps without an upper bound. Concurrent requests such as ?delay=999999 can retain all available Live/Puma threads for days and prevent the app from serving other requests.
| delay_seconds = (params[:delay] || 5).to_i | |
| delay_seconds = Integer(params[:delay] || 5, exception: false)&.clamp(0, 30) || 5 |
Context Used: CLAUDE.md (source)
|
|
||
| # Determine which section this chunk belongs to based on time | ||
| # Section N receives content arriving at time >= N * delay_seconds | ||
| section_index = [(elapsed / delay_seconds).floor, section_count - 1].min |
There was a problem hiding this comment.
The task accepts delay_seconds=0, but the first response chunk makes elapsed / delay_seconds infinite and .floor raises FloatDomainError. The advertised generator then exits before writing a usable cache; non-positive delays and section counts need validation before capture starts.
Context Used: CLAUDE.md (source)
| total_timeout = (section_count * delay_seconds) + 30 | ||
|
|
||
| # Use IO.popen with unbuffered curl for streaming | ||
| IO.popen(["curl", "-sN", "--max-time", total_timeout.to_s, uri.to_s], "r") do |io| |
There was a problem hiding this comment.
Failed Capture Reports Success
The subprocess status is never checked. If curl cannot connect or times out after a partial response, the task still writes empty or truncated section files and prints that generation completed, so the cached endpoint later serves broken HTML or incomplete React reveal scripts.
Context Used: CLAUDE.md (source)
| section_index = [(elapsed / delay_seconds).floor, section_count - 1].min | ||
|
|
||
| sections[section_index] << chunk |
There was a problem hiding this comment.
Arrival Time Mislabels Sections
Wall-clock windows do not preserve React boundary identity: server startup, scheduling, and transport buffering can move a chunk into a later bucket, and one chunk may contain multiple boundary reveals. Replaying those inferred files can reveal a section at the wrong time or leave a fallback permanently pending; the capture needs explicit section markers or independently captured boundaries.
Context Used: CLAUDE.md (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| sections.each_with_index do |content, index| | ||
| filename = "section#{index}.html" | ||
| filepath = output_dir.join(filename) | ||
| File.write(filepath, content.force_encoding("UTF-8")) |
There was a problem hiding this comment.
Live Cache Files Are Truncated
File.write truncates each file that the cached endpoint reads without synchronization. A request during regeneration can read an empty or partially written $RC script and receive malformed streamed HTML; generate into temporary files or a temporary directory and publish the complete set atomically.
Context Used: CLAUDE.md (source)
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0b88c86f4a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
|
||
| # Determine which section this chunk belongs to based on time | ||
| # Section N receives content arriving at time >= N * delay_seconds | ||
| section_index = [(elapsed / delay_seconds).floor, section_count - 1].min |
There was a problem hiding this comment.
Reject a zero section delay before dividing by it
When callers use delay_seconds=0 to generate an immediately available cache (a valid counterpart to the cached endpoint's ?delay=0), elapsed / delay_seconds becomes Infinity and .floor raises FloatDomainError as soon as curl emits the first chunk. Validate that the delay is positive or handle the zero-delay case without time-window division.
Useful? React with 👍 / 👎.
| IO.popen(["curl", "-sN", "--max-time", total_timeout.to_s, uri.to_s], "r") do |io| | ||
| # Read in small chunks for better timing accuracy | ||
| while (chunk = io.read_nonblock(4096, exception: false)) | ||
| break if chunk.nil? # EOF | ||
|
|
||
| if chunk == :wait_readable | ||
| io.wait_readable(0.01) | ||
| next | ||
| end | ||
|
|
||
| elapsed = Time.now - section_start_time | ||
|
|
||
| # Determine which section this chunk belongs to based on time | ||
| # Section N receives content arriving at time >= N * delay_seconds | ||
| section_index = [(elapsed / delay_seconds).floor, section_count - 1].min | ||
|
|
||
| sections[section_index] << chunk | ||
| end | ||
| end |
There was a problem hiding this comment.
Fail cache generation when curl cannot complete
When the dummy server is unavailable, returns a connection failure, or exceeds --max-time, curl exits nonzero but its status is never checked. The task then writes empty or partial section*.html files and reports completion, replacing a previously usable cache; the cached route will subsequently stream corrupted content. Check curl's exit status before writing the section files.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb`:
- Line 307: Update the delay parsing near delay_seconds in the streaming action
to use Integer(..., exception: false), handle invalid or negative values safely,
and clamp valid delays to a small demo maximum so sleep never receives negatives
or unbounded durations. Preserve the existing default delay when the parameter
is missing or invalid.
- Line 17: Remove ActionController::Live from PagesController and move
selective_hydration_cached into a separate small controller that inherits from
PagesController or the appropriate base and includes ActionController::Live.
Update routing or references to target the new Live-only controller while
keeping all other PagesController actions on the normal request path.
In `@react_on_rails_pro/spec/dummy/client/app/components/CacheSection.jsx`:
- Around line 25-27: Update the Promise executor in the delay logic within
CacheSection so the setTimeout call is enclosed in a block body and does not
implicitly return its timeout ID, while preserving the existing delay behavior.
In `@react_on_rails_pro/spec/dummy/lib/tasks/section_cache.rake`:
- Line 114: Update the cache-generation instructions in section_cache.rake at
lines 114-114 and pages_controller.rb at lines 316-316 to invoke the task with
bundle exec, preserving the existing task names and arguments.
- Around line 57-76: The streaming fetch around IO.popen must validate curl’s
child exit status before publishing cache output. Capture the process status
from the IO.popen block, detect non-successful termination including timeout or
connection failure, and abort the task before writing any partial or empty cache
files; preserve normal section processing for successful fetches.
- Around line 176-181: Harden the :clean task’s output_dir handling by rejecting
route values containing "." or ".." path segments, then expand the candidate
path and verify it remains beneath Rails.root.join("public", "cache") before
calling FileUtils.rm_rf. Preserve the existing cleanup behavior for valid routes
and skip deletion when validation fails.
- Around line 90-100: Update the section cache generation flow around the output
directory and sections.each_with_index to write all captured files into a
uniquely named temporary directory, then atomically replace the existing route
cache directory only after every write succeeds. Ensure failures leave the
existing cache intact and remove or clean up the temporary directory as
appropriate.
🪄 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: 6c7f9d90-0bdb-47ae-b69c-6824306bc854
📒 Files selected for processing (11)
react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rbreact_on_rails_pro/spec/dummy/app/views/pages/selective_hydration_demo.html.erbreact_on_rails_pro/spec/dummy/client/app/components/CacheSection.jsxreact_on_rails_pro/spec/dummy/client/app/components/SelectiveHydration/SelectiveHydrationArticleSection.jsxreact_on_rails_pro/spec/dummy/client/app/components/SelectiveHydration/SelectiveHydrationCategoriesSection.jsxreact_on_rails_pro/spec/dummy/client/app/components/SelectiveHydration/SelectiveHydrationFooterSection.jsxreact_on_rails_pro/spec/dummy/client/app/components/SelectiveHydration/SelectiveHydrationHeaderSection.jsxreact_on_rails_pro/spec/dummy/client/app/packs/server-bundle.jsreact_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/SelectiveHydrationDemo.jsxreact_on_rails_pro/spec/dummy/config/routes.rbreact_on_rails_pro/spec/dummy/lib/tasks/section_cache.rake
| # https://github.com/shakacode/react_on_rails/blob/main/REACT-ON-RAILS-PRO-LICENSE.md | ||
|
|
||
| class PagesController < ApplicationController # rubocop:disable Metrics/ClassLength | ||
| include ActionController::Live |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
rg -n -C3 'include ActionController::Live|class PagesController' \
react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb
fd -a 'Gemfile.lock' . --exec rg -n 'actionpack \(' {}Repository: shakacode/react_on_rails
Length of output: 2079
🏁 Script executed:
#!/bin/bash
set -eu
file='react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb'
# Show the relevant controller sections around the Live include and the line numbers mentioned in the review.
sed -n '1,60p' "$file"
printf '\n---\n'
sed -n '290,330p' "$file"
printf '\n---\n'
sed -n '330,360p' "$file"Repository: shakacode/react_on_rails
Length of output: 5216
Isolate ActionController::Live to the streaming action.
react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb:17 — including ActionController::Live here changes the threading model for every PagesController action. Move selective_hydration_cached into a small Live-only controller so the rest of the controller stays on the normal request path.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb` at line
17, Remove ActionController::Live from PagesController and move
selective_hydration_cached into a separate small controller that inherits from
PagesController or the appropriate base and includes ActionController::Live.
Update routing or references to target the new Live-only controller while
keeping all other PagesController actions on the normal request path.
| def selective_hydration_cached # rubocop:disable Metrics/AbcSize | ||
| # Stream pre-cached section files with delays using ActionController::Live | ||
| # This simulates serving cached SSR content with progressive streaming | ||
| delay_seconds = (params[:delay] || 5).to_i |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Bound and validate the streaming delay.
A negative value raises from sleep, while an arbitrarily large value occupies a Live worker thread indefinitely. Parse with Integer(..., exception: false) and clamp to a small demo limit.
Proposed validation
- delay_seconds = (params[:delay] || 5).to_i
+ requested_delay = Integer(params.fetch(:delay, 5), exception: false)
+ delay_seconds = (requested_delay || 5).clamp(0, 30)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| delay_seconds = (params[:delay] || 5).to_i | |
| requested_delay = Integer(params.fetch(:delay, 5), exception: false) | |
| delay_seconds = (requested_delay || 5).clamp(0, 30) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb` at line
307, Update the delay parsing near delay_seconds in the streaming action to use
Integer(..., exception: false), handle invalid or negative values safely, and
clamp valid delays to a small demo maximum so sleep never receives negatives or
unbounded durations. Preserve the existing default delay when the parameter is
missing or invalid.
| if (delayMs > 0) { | ||
| await new Promise((resolve) => setTimeout(resolve, delayMs)); | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Fix ESLint no-promise-executor-return error.
Arrow functions without braces implicitly return the result of their expression. setTimeout returns a timeout ID, which violates the rule that promise executors should return void. Wrap the setTimeout call in braces to avoid returning a value.
🛠️ Proposed fix
if (delayMs > 0) {
- await new Promise((resolve) => setTimeout(resolve, delayMs));
+ await new Promise((resolve) => {
+ setTimeout(resolve, delayMs);
+ });
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (delayMs > 0) { | |
| await new Promise((resolve) => setTimeout(resolve, delayMs)); | |
| } | |
| if (delayMs > 0) { | |
| await new Promise((resolve) => { | |
| setTimeout(resolve, delayMs); | |
| }); | |
| } |
🧰 Tools
🪛 ast-grep (0.44.1)
[warning] 25-25: Avoid using the initial state variable in setState
Context: setTimeout(resolve, delayMs)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(setstate-same-var)
🪛 ESLint
[error] 26-26: Return values from promise executor functions cannot be read.
(no-promise-executor-return)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@react_on_rails_pro/spec/dummy/client/app/components/CacheSection.jsx` around
lines 25 - 27, Update the Promise executor in the delay logic within
CacheSection so the setTimeout call is enclosed in a block body and does not
implicitly return its timeout ID, while preserving the existing delay behavior.
| # Use IO.popen with unbuffered curl for streaming | ||
| IO.popen(["curl", "-sN", "--max-time", total_timeout.to_s, uri.to_s], "r") do |io| | ||
| # Read in small chunks for better timing accuracy | ||
| while (chunk = io.read_nonblock(4096, exception: false)) | ||
| break if chunk.nil? # EOF | ||
|
|
||
| if chunk == :wait_readable | ||
| io.wait_readable(0.01) | ||
| next | ||
| end | ||
|
|
||
| elapsed = Time.now - section_start_time | ||
|
|
||
| # Determine which section this chunk belongs to based on time | ||
| # Section N receives content arriving at time >= N * delay_seconds | ||
| section_index = [(elapsed / delay_seconds).floor, section_count - 1].min | ||
|
|
||
| sections[section_index] << chunk | ||
| end | ||
| end |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Abort when the streaming fetch fails.
curl may time out or fail to connect, but its exit status is ignored; the task then writes partial or empty files and reports success. Check the child status before publishing any cache output.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@react_on_rails_pro/spec/dummy/lib/tasks/section_cache.rake` around lines 57 -
76, The streaming fetch around IO.popen must validate curl’s child exit status
before publishing cache output. Capture the process status from the IO.popen
block, detect non-successful termination including timeout or connection
failure, and abort the task before writing any partial or empty cache files;
preserve normal section processing for successful fetches.
| # Create output directory | ||
| output_dir = Rails.root.join("public", "cache", route.gsub(%r{^/}, "").tr("/", "_")) | ||
| FileUtils.mkdir_p(output_dir) | ||
|
|
||
| # Write each section (force UTF-8 encoding) | ||
| sections.each_with_index do |content, index| | ||
| filename = "section#{index}.html" | ||
| filepath = output_dir.join(filename) | ||
| File.write(filepath, content.force_encoding("UTF-8")) | ||
| puts "Wrote #{filepath} (#{content.bytesize} bytes)" | ||
| end |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Replace the cache directory atomically.
Regenerating with a smaller section_count leaves old sectionN.html files behind, and cached replay will continue streaming them. Write into a temporary directory, then replace the existing directory only after successful capture.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@react_on_rails_pro/spec/dummy/lib/tasks/section_cache.rake` around lines 90 -
100, Update the section cache generation flow around the output directory and
sections.each_with_index to write all captured files into a uniquely named
temporary directory, then atomically replace the existing route cache directory
only after every write succeeds. Ensure failures leave the existing cache intact
and remove or clean up the temporary directory as appropriate.
| output_dir = Rails.root.join("public", "cache", route.gsub(%r{^/}, "").tr("/", "_")) | ||
|
|
||
| unless Dir.exist?(output_dir) | ||
| abort "Output directory not found: #{output_dir}\nRun 'rake section_cache:generate' first." |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Use bundle exec in both cache-generation instructions.
react_on_rails_pro/spec/dummy/lib/tasks/section_cache.rake#L114-L114: usebundle exec rake section_cache:generate.react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb#L316-L316: usebundle exec rake section_cache:generate[...].
As per coding guidelines, “Use bundle exec for Ruby commands.” <coding_guidelines>
📍 Affects 2 files
react_on_rails_pro/spec/dummy/lib/tasks/section_cache.rake#L114-L114(this comment)react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb#L316-L316
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@react_on_rails_pro/spec/dummy/lib/tasks/section_cache.rake` at line 114,
Update the cache-generation instructions in section_cache.rake at lines 114-114
and pages_controller.rb at lines 316-316 to invoke the task with bundle exec,
preserving the existing task names and arguments.
Source: Coding guidelines
| task :clean, [:route] => :environment do |_t, args| | ||
| route = args[:route] || "/selective_hydration_demo" | ||
| output_dir = Rails.root.join("public", "cache", route.gsub(%r{^/}, "").tr("/", "_")) | ||
|
|
||
| if Dir.exist?(output_dir) | ||
| FileUtils.rm_rf(output_dir) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Prevent cleanup from escaping the section-cache directory.
With route="..", output_dir resolves to public/cache/.., and rm_rf deletes the entire public directory. Reject dot segments and verify the expanded path remains beneath public/cache before deletion.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@react_on_rails_pro/spec/dummy/lib/tasks/section_cache.rake` around lines 176
- 181, Harden the :clean task’s output_dir handling by rejecting route values
containing "." or ".." path segments, then expand the candidate path and verify
it remains beneath Rails.root.join("public", "cache") before calling
FileUtils.rm_rf. Preserve the existing cleanup behavior for valid routes and
skip deletion when validation fails.
Summary
Experimental implementation for investigating selective hydration caching in the dummy app:
All changes are in
react_on_rails_pro/spec/dummy(experimental).How it works
Generate cached sections:
This fetches the page with section delays
[0, 5000, 10000, 15000]ms, capturing HTML chunks into time-based buckets.Serve cached sections with progressive streaming:
curl http://localhost:5150/selective_hydration_cached?delay=1Streams each section with configurable delays, replacing CSP nonces dynamically.
Progressive rendering in browser:
$RCcallsTest plan
rake section_cache:generatecreates section files/selective_hydration_cachedstreams sections progressively🤖 Generated with Claude Code
Summary by CodeRabbit