Conversation
First of three stacked sub-PRs enabling React Server Components in this tutorial, since `enable_rsc_support` is a Pro-only configuration. Pure dependency swap — ExecJS server rendering and existing behavior unchanged. - Gemfile: `react_on_rails 16.6.0.rc.0` → `react_on_rails_pro 16.6.0` - package.json: `react-on-rails 16.6.0-rc.0` → `react-on-rails-pro 16.6.0` - shakapacker: `10.0.0.rc.0` → `10.0.0` stable (lockstep with the Pro gem) - First-party imports: `'react-on-rails'` → `'react-on-rails-pro'` across 10 files (matches `react_on_rails :pro` generator's `update_imports_to_pro_package`) - `config/webpack/commonWebpackConfig.js`: alias `'react-on-rails$'` → `'react-on-rails-pro'` as a third-party shim. Without it, `rescript-react-on-rails`'s `import ReactOnRails from "react-on-rails"` resolves to core, core and Pro coexist in the bundle, and SSR fails with the ExecJS error `Cannot mix react-on-rails (core) with react-on-rails-pro` (verified by repro). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WalkthroughThis pull request adds comprehensive React Server Components (RSC) and Node renderer support to the application. It updates core dependencies from Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client Browser
participant Rails as Rails Server
participant NodeRenderer as Node Renderer
participant DB as Database
Client->>Rails: GET /server-components
Rails->>DB: Fetch Comments
DB-->>Rails: Comments[]
Rails->>NodeRenderer: Stream RSC Payload<br/>(ServerComponentsPage with comments)
NodeRenderer->>NodeRenderer: Render Server Components<br/>(ServerInfo, CommentsFeed,<br/>LiveActivity)
NodeRenderer-->>Rails: NDJSON Chunks<br/>(HTML segments)
Rails-->>Client: HTTP 200 + Streamed HTML
Client->>Client: Hydrate & Mount Components<br/>(Client Islands: TogglePanel,<br/>LiveActivityRefresher)
Client->>Rails: GET /rsc_payload/live-activity<br/>(on refresh)
Rails->>NodeRenderer: Stream LiveActivity<br/>RSC Payload
NodeRenderer-->>Rails: Updated Activity HTML
Rails-->>Client: NDJSON Stream
Client->>Client: Error Boundary Fallback<br/>(on error)
Client->>Rails: GET /rsc_payload/live-activity<br/>(retry)
Rails->>NodeRenderer: Re-render LiveActivity
NodeRenderer-->>Rails: HTML Chunk
Rails-->>Client: Success HTML
Client->>Client: Reset Error Boundary<br/>& Display Content
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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 |
🚀 Quick Review App CommandsWelcome! Here are the commands you can use in this PR:
|
Code Review — Sub-PR 1: Dependency Swap (react-on-rails → react-on-rails-pro)OverviewThis PR is the integration base branch for the React on Rails Pro + RSC migration. The diff from Sub-PR 1 (#726) performs a clean dependency swap: ✅ What's done well
|
…#728) * Switch SSR to the Pro Node renderer on webpack Second of three stacked sub-PRs in the Pro RSC migration. Routes all server rendering through the Pro Node renderer (port 3800) instead of ExecJS, and flips the asset bundler from rspack to webpack — scoped reversal of #702, needed because rspack 2.0.0-beta.7's webpack compatibility layer doesn't cover the APIs upstream RSCWebpackPlugin requires. We flip back to rspack once shakacode/react_on_rails_rsc#29 ships a native rspack RSC plugin. The bundler flip and NodeRenderer wiring ship atomically: the server bundle produced by the Pro webpack transforms (target: 'node' + libraryTarget: 'commonjs2') is not evaluable by ExecJS, so the initializer pointing server_renderer at the NodeRenderer must land at the same time. Key changes: - config/shakapacker.yml: assets_bundler: rspack → webpack - config/webpack/bundlerUtils.js: return @rspack/core or webpack based on the shakapacker setting (was rspack-only and threw otherwise); spec updated in parallel - config/webpack/serverWebpackConfig.js: Pro transforms per the :pro generator's update_webpack_config_for_pro and the marketplace/dummy references — target: 'node' + node: false, libraryTarget: 'commonjs2', extractLoader helper, babelLoader.options.caller = { ssr: true }, destructured module.exports so Sub-PR 3's rscWebpackConfig.js can derive from serverWebpackConfig(true). RSCWebpackPlugin({ isServer: true }) when !rscBundle emits the server manifest; inert until Sub-PR 3 activates RSC support - config/initializers/react_on_rails_pro.rb: NodeRenderer-only config (no RSC fields — those move in Sub-PR 3) - renderer/node-renderer.js: launcher with strict integer env parsing, CI worker cap, and additionalContext: { URL, AbortController } so react-router-dom's NavLink.encodeLocation does not throw "ReferenceError: URL is not defined" at SSR time - Procfile.dev: renderer: NODE_ENV=development node renderer/node-renderer.js - package.json: react-on-rails-pro-node-renderer 16.6.0 and react-on-rails-rsc ^19.0.4 (Pro peer dep; required for the RSCWebpackPlugin import) - .gitignore: /renderer/.node-renderer-bundles/ - .env.example: REACT_ON_RAILS_PRO_LICENSE, RENDERER_PASSWORD, and REACT_RENDERER_URL with dev vs prod guidance - .github/workflows/rspec_test.yml: start the Node renderer before rspec with PID liveness and port-ready checks plus log capture on failure Verified locally: webpack build compiles cleanly. `bin/rails s` on 3000 with `node renderer/node-renderer.js` on 3800 renders GET / at HTTP 200; Rails log shows "Node Renderer responded" and the renderer log emits "[SERVER] RENDERED Footer to dom node with id: ..." — confirming SSR went through the Pro path rather than falling back to ExecJS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Tighten Pro webpack + initializer setup from reference audit Four fixes from auditing Sub-PR 2 against the Pro dummy, the :pro generator, and the Pro configuration docs. - config/initializers/react_on_rails_pro.rb: renderer_password now raises in non-local envs instead of falling back to the dev string. Previously any production deploy that forgot to set the env var would silently run with a known-public password; now it fails loudly. Matches the safer pattern from PR #723's final state. - config/webpack/serverWebpackConfig.js: pass clientReferences to RSCWebpackPlugin({ isServer: true }), matching the Pro dummy's serverWebpackConfig at `react_on_rails_pro/spec/dummy/config/webpack/ serverWebpackConfig.js`. Without it, the plugin may walk into node_modules and hit unlodaed .tsx source files and re-scan modules we don't need. Locks client-ref discovery to client/app/**. - config/webpack/serverWebpackConfig.js: drop publicPath from the server-bundle output. Server bundles are loaded by the Node renderer via the filesystem, never served over HTTP — the URL is unused. Matches the Pro dummy's comment. - package.json: pin react-on-rails-rsc to 19.0.4 stable (was ^19.0.4 range) and add "node-renderer" npm script as a convenience shortcut for `node renderer/node-renderer.js`. - .github/workflows/rspec_test.yml: set RENDERER_PASSWORD explicitly in CI to the shared dev default so both the initializer and the launcher use the same concrete value, avoiding silent drift if either side's default is ever touched. Re-verified: webpack build clean; renderer + Rails boot; GET / returns HTTP 200 with "Node Renderer responded" in the Rails log and "[SERVER] RENDERED" in the renderer log, confirming SSR still goes through the Pro path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Align renderer_password initializer with Pro docs + source Previous commit added Rails.env-branching + explicit raise in non-local envs. That duplicates what Pro's configuration.rb already does at boot (validate_renderer_password_for_production) and diverges from the documented pattern in pro/node-renderer.md and pro/installation.md. Revert to the simple ENV.fetch with a dev default. Pro handles the prod-enforcement itself — if RENDERER_PASSWORD is unset in a production-like env, Pro raises at boot with a helpful error message pointing at exactly this fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Pin react and react-dom to 19.0.4 Journey plan's Sub-PR 1 KEEP table explicitly called for the 19.0.4 pin from PR #723's final state — missed in the Sub-PR 1 migration commit. 19.0.4 is the minimum React version documented as required for React Server Components (per the Pro RSC tutorial doc, CVE-safe floor). Tightens from the inherited "^19.0.0" range. Boot-verified: webpack build clean; renderer + Rails boot; GET / returns HTTP 200 with "Node Renderer responded" in Rails log and "[SERVER] RENDERED" in renderer log — SSR path unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Rename Procfile.dev renderer process to node-renderer Match the Pro dummy's Procfile.dev and PR #723's naming. Marketplace uses `renderer:`, but the dummy is the canonical Pro reference and aligns with how the :pro generator's `add_pro_to_procfile` names it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Default renderer logLevel to 'info' Pro docs (docs/oss/building-features/node-renderer/container-deployment.md) prescribe `logLevel: 'info'` as the general-renderer-logs default, and the Pro dummy's renderer/node-renderer.js also uses 'info'. The inherited 'debug' default is too verbose for normal operation (emits the full VM context setup messages like "Adding Buffer, TextDecoder, ..."). Debug remains reachable via RENDERER_LOG_LEVEL=debug env var as docs describe for active debugging (docs/oss/building-features/node-renderer/ debugging.md). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Trim over-verbose comments across Sub-PR 2 files Per the repo's CLAUDE.md guidance ("default to writing no comments; only when the WHY is non-obvious"), stripping comments that explain WHAT the code does or reference the current-PR context. Kept (non-obvious WHYs): the `additionalContext: { URL, AbortController }` reason (react-router-dom NavLink url-is-not-defined), the CI worker-count cap reason, `clientReferences` scoping rationale, `libraryTarget: 'commonjs2'` requirement for the renderer, `target: 'node'` fix for SSR-breaking libs, the renderer-fallback-disabled reason, the renderer-password-must-match pointer, the bundler-utils dual-support blurb, and the shakapacker.yml tactical-reversal tag. Dropped (WHAT explanations, redundant with identifiers): JSDoc blocks describing `configureServer` and `extractLoader`, per-config-key descriptions in the renderer launcher, Procfile.dev process-purpose comment, verbose `.env.example` prose, initializer multi-line explanations of each field. No code changes — comments only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Restore license setup guidance in initializer comments Over-trimmed in the previous cleanup commit. The longer wording has non-obvious info (where to get the JWT, which envs skip license, what Pro does when no license is set) that readers genuinely benefit from — beyond what the generator template's one-line version captures. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Wire the Pro Node renderer for Control Plane deploys Sub-PR 2 already wires dev (Procfile.dev) and CI (rspec_test.yml). Without this commit, merging base → master with the renderer active on the Rails side but no renderer process in the deployed container would break the live tutorial (Rails tries to hit localhost:3800, nothing listens, HTTPX::ConnectionError → 500 with renderer_use_fallback_exec_js = false). Matches the react-server-components-marketplace-demo deploy pattern (Option 1 "Single Container" from docs/oss/building-features/ node-renderer/container-deployment.md): - .controlplane/Dockerfile: CMD starts the Node renderer and Rails in the same container with `wait -n ; exit 1` so any child exit restarts the whole workload. Preserves the existing Thruster HTTP/2 proxy for Rails — the only deviation from marketplace's literal CMD. - .controlplane/templates/app.yml: add RENDERER_PORT, RENDERER_LOG_LEVEL, RENDERER_WORKERS_COUNT, RENDERER_URL as plain env, plus RENDERER_PASSWORD and REACT_ON_RAILS_PRO_LICENSE as Control Plane secret references keyed by {{APP_NAME}}-secrets (matches the repo's existing {{APP_NAME}} templating convention in DATABASE_URL / REDIS_URL). - .controlplane/templates/rails.yml: bump memory 512Mi → 2Gi. The container now runs two processes; 512Mi OOMs fast. 2Gi matches the marketplace demo's rails.yml. - config/initializers/react_on_rails_pro.rb: read ENV["RENDERER_URL"] instead of ENV["REACT_RENDERER_URL"]. Aligns with docs/oss/ configuration/configuration-pro.md (which uses RENDERER_URL), the Pro dummy's initializer, and the marketplace initializer — all of which use RENDERER_URL without a REACT_ prefix. The REACT_ prefix was inherited from PR #723 and is non-canonical. - .env.example: matching REACT_RENDERER_URL → RENDERER_URL rename. Prerequisite before first deploy (outside PR scope, one-time per app per org, performed manually via cpln or the Control Plane UI): create a Secret named `<app-name>-secrets` with keys RENDERER_PASSWORD (strong random) and REACT_ON_RAILS_PRO_LICENSE (JWT from pro.reactonrails.com). Affects react-webpack-rails-tutorial-production, react-webpack-rails-tutorial-staging, and any qa-* review apps. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Use {{APP_SECRETS}} for renderer + license secret references {{APP_NAME}}-secrets expanded to a per-app secret name, which would require a new Control Plane Secret for every review app PR — wrong per cpflow's own conventions. cpflow exposes {{APP_SECRETS}} (lib/core/template_parser.rb:49, lib/core/config.rb:51-52) which expands to `{APP_PREFIX}-secrets`. Per our controlplane.yml, APP_PREFIX is: - `react-webpack-rails-tutorial-production` for the prod app - `react-webpack-rails-tutorial-staging` for the staging app - `qa-react-webpack-rails-tutorial` for all qa-* review apps (because match_if_app_name_starts_with: true) So review apps all share `qa-react-webpack-rails-tutorial-secrets` instead of each PR needing its own. Three secrets total across two orgs instead of one per PR. Matches the `{APP_PREFIX}-secrets` default documented at shakacode/control-plane-flow/docs/secrets-and-env-values.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fix env var name mismatch: NODE_RENDERER_CONCURRENCY → RENDERER_WORKERS_COUNT The launcher was reading NODE_RENDERER_CONCURRENCY (inherited from PR #723's f55dcc2), but app.yml sets RENDERER_WORKERS_COUNT (canonical per marketplace + Pro renderer library). Result: prod/staging would ignore the deployed value and always use the launcher default. RENDERER_WORKERS_COUNT is the canonical env var name per: - docs/oss/building-features/node-renderer/js-configuration.md "workersCount (default: process.env.RENDERER_WORKERS_COUNT ...)" - packages/react-on-rails-pro-node-renderer/src/shared/configBuilder.ts:200 "workersCount: env.RENDERER_WORKERS_COUNT ? parseInt(...) : defaultWorkersCount()" - react-server-components-marketplace-demo/node-renderer.js NODE_RENDERER_CONCURRENCY was a non-canonical name invented by PR #723. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Right-size rails workload: 1Gi + capacityAI Previous commit bumped memory 512Mi → 2Gi matching the marketplace demo, but cherry-picked only the memory dimension: CPU stayed at 300m and capacityAI stayed false. Net result was a wasteful fixed 2Gi allocation with no autoscale — the pattern Justin flagged. Better right-size for this tutorial (smaller Rails surface than the marketplace RSC demo): - memory: 2Gi → 1Gi. Enough headroom for Rails + the Pro Node renderer in one container without reserving capacity that won't be used. - capacityAI: false → true. Adjusts CPU/memory within the single replica based on observed usage, so the 1Gi/300m baseline grows if actual workload warrants it. Matches the marketplace demo's capacityAI posture without copying its oversized static baseline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Replace org.yml placeholder with renderer + license schema Document the actual keys the qa-* dictionary needs (RENDERER_PASSWORD, REACT_ON_RAILS_PRO_LICENSE) instead of the unused SOME_ENV placeholder, and warn against re-applying the template after real values are populated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fix stale Rspack comments in Procfile.dev The rspack→webpack bundler flip in this PR left two comment lines referring to Rspack that now misdescribe the active bundler. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Treat blank RENDERER_PASSWORD as unset in Rails initializer ENV.fetch returns "" when the env var is set to empty string, while the JS renderer's `process.env.RENDERER_PASSWORD || fallback` treats "" as falsy. Result: copying .env.example verbatim (ships with a blank RENDERER_PASSWORD= line) leaves Rails sending "" in the auth header while the renderer expects the dev default, silently failing SSR auth. Switch to ENV["RENDERER_PASSWORD"].presence || default so blank values route to the fallback on both sides. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Document RENDERER_PORT, LOG_LEVEL, WORKERS_COUNT in .env.example app.yml and the renderer launcher read three additional env vars that .env.example didn't mention, leaving developers with no single place to see which renderer knobs exist. Add them commented-out with their defaults and brief descriptions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Derive RSCWebpackPlugin clientReferences from config.source_path Previously hardcoded path.resolve(__dirname, '../../client/app'), which would silently point at the wrong directory if shakapacker.yml's source_path were ever changed. The same file already derives the server-bundle entry's path from config.source_path (line 33); apply the same pattern here so the two stay in sync. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Dump Node renderer log on CI rspec failure The renderer startup step only cats /tmp/node-renderer.log when the startup wait loop times out (30s). If the renderer starts fine but crashes mid-test, the log is never surfaced, making the rspec failure impossible to debug from the Actions UI. Add an `if: failure()` step after rspec that cats the log so any crash during the test run is visible. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Forward SIGTERM to children in Dockerfile CMD Bash as PID 1 doesn't forward signals to its children by default, so Control Plane's rolling-deploy SIGTERM never reaches Puma or the Node renderer's cluster manager. Both handle SIGTERM automatically (drain in-flight requests, stop accepting new ones), but only if they receive the signal. Without a trap, the graceful period expires and SIGKILL drops in-flight requests on every rolling deploy. Add `trap 'kill -TERM $RENDERER_PID $RAILS_PID' TERM INT` before `wait -n`. Uses the PID captures that were previously assigned but unused, turning dead code into a real graceful-shutdown mechanism aligned with the graceful-shutdown section of container-deployment.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Revert "Forward SIGTERM to children in Dockerfile CMD" This reverts commit 1bd5e86. The trap addition was incomplete. On the shutdown signal (Control Plane sends SIGINT per docs.controlplane.com/reference/workload/termination.md), the trap fires, `wait -n` returns 130, and `exit 1` then runs unconditionally. The container exits with code 1 on every rolling deploy instead of a code that reflects the signal-initiated shutdown. More importantly, the trap was trying to solve a problem Control Plane already handles. CP's default preStop hook runs `sh -c "sleep 45"` before any signal reaches PID 1, and the sidecar stops accepting new inbound connections ~80 seconds ahead of termination. That sleep plus the routing drain is the graceful-shutdown window; signal forwarding inside the container is marginal on top of it. Match the reference deployments verbatim: the react-on-rails-demo- marketplace-rsc Dockerfile and the hichee production Dockerfile both use this exact bash-c pattern without a trap. If deeper graceful shutdown is ever needed, the right tool is extending preStop or switching to a process manager (overmind, tini), not a bash trap on wait -n. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Guard against dev-default RENDERER_PASSWORD in production The Pro renderer's JS side raises if RENDERER_PASSWORD is unset in production, but accepts the literal "local-dev-renderer-password" value. A CP secret misconfigured to that string (easy to happen when copying from .env.example) would let both sides "match" while running with no real authentication. Split the branch by Rails.env.local?: - dev/test keeps the .presence || default fallback so blank env vars still work for local development - production fetches strict and raises if blank, unset, or the literal dev default Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fold CI workers cap into parseIntegerEnv default The post-hoc `if (process.env.CI && env == null) config.workersCount = 2` block mutated an already-constructed config and used a narrower definition of "unset" (`== null`) than parseIntegerEnv's own check (treats "" as unset too). Folding the CI default into the second argument of parseIntegerEnv: workersCount: parseIntegerEnv('RENDERER_WORKERS_COUNT', process.env.CI ? 2 : 3, { min: 0 }) keeps the same behaviour for explicit values, uses one definition of "unset" consistently, and drops the mutation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Document rscBundle parameter's Sub-PR 3 role The parameter is unused until Sub-PR 3 wires rscWebpackConfig.js to call configureServer(true). Adding a short comment so it isn't mistaken for dead code during review. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Revert "Document rscBundle parameter's Sub-PR 3 role" This reverts commit a7734472b8e9e2bf80088a85ef0b7b9dd76b44a0. The `rscBundle = false` parameter shape follows the documented Pro pattern: docs/oss/migrating/rsc-preparing-app.md:244 and docs/pro/react-server-components/upgrading-existing-pro-app.md:106 both name it by parameter name and show the exact `if (!rscBundle) { ... RSCWebpackPlugin ... }` guard. The Pro dummy (react_on_rails_pro/spec/dummy/config/webpack/serverWebpackConfig.js) uses the same shape with no inline comment. The reverted commit restated what the docs already cover and added a Sub-PR 3 reference that would rot once Sub-PR 3 merges. Code follows documented conventions; readers who need context can read the doc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Loosen react pin from exact 19.0.4 to ~19.0.4 The exact pin locked out every 19.0.x patch including 19.0.5 and future security patches within the 19.0 line. Pro's own install docs (docs/pro/react-server-components/upgrading-existing-pro-app.md:26-28 and create-without-ssr.md:37) prescribe `react@~19.0.4` — tilde range that keeps the CVE floor while allowing 19.0.x patches. react-on-rails-rsc@19.0.4's peer dep is `react: ^19.0.3`, so 19.0.5 satisfies it. After this change, yarn resolves react to 19.0.5. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Trim renderer_password initializer comment The previous comment restated in English what the code below already expressed. Cut to the one non-obvious WHY (why the prod branch exists despite Pro's own JS-side guard). Method names + the raise message cover the rest. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Align CI renderer log handling with Pro CI Pro's own integration CI (react_on_rails_pro/.github/workflows/ pro-integration-tests.yml) runs the renderer with `pnpm run node-renderer &` — no log redirect — so renderer output interleaves with job stdout and is always visible without a special dump step. Our workflow inherited a redirect-to-/tmp/node-renderer.log pattern from PR #723 plus a follow-up if-failure cat step (commit 7dea094) that dumped the file after rspec. Both existed to work around the redirect; neither was in any reference. Drop the redirect and the associated cat calls (startup-failure cat, timeout cat, post-rspec failure dump). Renderer logs now appear inline with job output, same shape as Pro CI. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Reframe shakapacker bundler note as TODO "Tactical:" read as a weird label; "TODO:" is the standard signal that the current state is meant to be reverted once the upstream blocker (shakacode/react_on_rails_rsc#29) ships. Same content, leads with the action. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Remove RENDERER_PASSWORD prod-default guard The guard added in 0eb94af raised at every Rails boot in production, which breaks the Docker build step that runs `bin/rails react_on_rails:locale` during image bake (RENDERER_PASSWORD isn't in the build environment — secrets are mounted at runtime only, not at build time). Deploy on Control Plane fails before it can even produce an image. Reference check on the guard: zero of Pro dummy, marketplace demo (react-on-rails-demo-marketplace-rsc), Justin's PR 723, and hichee have a "raise on literal dev-default value" guard. Each uses ENV.fetch with a default or hardcodes the password. The guard was a reviewer-driven divergence with no reference backing. Revert the prod branch. Keep the .presence fallback from 7254b1a (that one responds to our specific .env.example shape, not a reference divergence). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code Review — PR #727 (Pro RSC Migration: Stacked Base)This is a thorough, well-documented migration. The inline rationale throughout is a strong signal that the tradeoffs (webpack-over-rspack temporarily, React version pin, single-container deployment) were thought through deliberately. Below is a full pass with a few items worth resolving before the stack lands on OverviewThe PR correctly splits work across three sub-PRs and integrates them here:
The overall approach aligns with canonical Pro references ( Issues1.
|
Adds a React Server Components demo page at /server-components, riding on top of Sub-PR 2's NodeRenderer + webpack setup. The page demonstrates four RSC capabilities: - Server Environment: ServerInfo reads Node's `os` module and lodash on the server; neither reaches the browser. - Interactive Client Component: a 'use client' TogglePanel nested inside a Server Component tree, demonstrating the donut pattern. - Live Server Activity: client-driven server-component re-fetching via useRSC().refetchComponent + RSCRoute, with react-error-boundary catching simulated errors and a Retry button that re-primes the cache before resetting the boundary. Local Suspense fallback prevents the in-flight RSC fetch from collapsing the page. - Streamed Comments: async Server Component receiving comments as props from the controller per the canonical Pro data-fetching pattern, streaming in via Suspense after the page shell. Renderer config (renderer/node-renderer.js) needs stubTimers: false (the default `true` no-ops setTimeout, which React's RSC server renderer uses for Flight-protocol yielding — without the override, RSC streams emit zero chunks and hang until the idle timeout fires) and replayServerAsyncOperationLogs: true (surfaces async-Server-Component console output through consoleReplayScript). Build setup uses upstream RSCWebpackPlugin and WebpackLoader. The RSC bundle reuses the server-bundle entry; entry name, RSC loader injection, and a few resolve overrides (react-server condition, react-dom/server: false) are the only differences from the SSR build. The webpack-config dispatch gains an RSC_BUNDLE_ONLY env-var gate matching the existing SERVER_BUNDLE_ONLY / CLIENT_BUNDLE_ONLY pattern; default builds now produce three bundles (client + server + RSC). Covered by request + system specs at spec/requests/server_components_spec.rb and spec/system/server_components_demo_spec.rb. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code Review — React on Rails Pro + RSC Migration (sub-PR stack base)OverviewThis is the integration branch for a well-structured stacked-PR migration: Bugs / CorrectnessDead code: Babel SSR caller on a SWC project Stale closure in Security
Naming / Clarity
Performance / BuildDefault build now produces three bundles Infrastructure
Minor
|
Style/TrailingCommaInArrayLiteral offense in the populated-comments RSC payload test. Didn't surface on #729 because lint_test.yml only triggers on PRs targeting master, so sub-PR CI skipped lint entirely. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
/deploy-review-app |
🎉 ✨ Deploy Complete! 🚀🌐 ➡️ Open Review AppDeployment successful for PR #727, commit 227fd25 🎮 Control Plane Console |
Code Review — PR #727 (Pro RSC migration integration branch)Overall: The migration is well-structured and the stacked-PR approach is the right call for a change this size. The RSC demo page clearly illustrates the key patterns (server-only imports, async components, A few issues worth addressing before this lands on 🔴 High — Dockerfile: SIGTERM not forwarded to child processesThe CMD ["bash", "-c", "node renderer/node-renderer.js & RENDERER_PID=$! ; bundle exec thrust bin/rails server & RAILS_PID=$! ; wait -n ; exit 1"]When Control Plane (or Kubernetes) sends A trap is the minimal fix: bash -c '
trap "kill $RENDERER_PID $RAILS_PID 2>/dev/null; wait" SIGTERM SIGINT
node renderer/node-renderer.js & RENDERER_PID=$!
bundle exec thrust bin/rails server & RAILS_PID=$!
wait -n
kill $RENDERER_PID $RAILS_PID 2>/dev/null
exit 1
'A proper process supervisor ( 🟡 Medium —
|
Sub-PR workflows skip lint_test.yml (it triggers only on PRs targeting master), so these slipped through into the integration branch: - SimpleCommentScreen.jsx: pre-existing line-form disable for max-classes-per-file broke when #729 inserted `'use client'` ahead of it (the rule reports at file position 1:1; line-form disable on the import below no longer covered). Switch to a block-form disable at file top. - ServerComponentsPage.jsx, LiveActivity.jsx, CommentsFeed.jsx: block-form disable for react/prop-types. React 19 removed runtime propTypes validation; the new components rely on ES default destructuring rather than dead PropTypes declarations. - LiveActivityRefresher.jsx: line-form disable for react/no-unstable-nested-components on the ErrorBoundary's fallbackRender prop. The render-prop API is canonical for react-error-boundary when the fallback needs a closure; it cannot be hoisted without losing access to parent state (refreshKey, setRefreshKey, refetchComponent via buildRetry). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ePanel React 19 removed runtime propTypes validation. The rule still ships in plugin:react/recommended (jsx-eslint/eslint-plugin-react#3753 tracks removing it) but no longer reflects this codebase's reality. - .eslintrc: set react/prop-types: 0 with rationale pointing at the upstream issue. - ServerComponentsPage / LiveActivity / CommentsFeed: drop the per-file `/* eslint-disable react/prop-types */` workarounds from 7b9093e; the rule is now off everywhere. - TogglePanel: drop the prop-types import and the `TogglePanel.propTypes` declaration we introduced; React 19 doesn't validate them. Existing components elsewhere with their own PropTypes are unaffected — the rule only ever required declarations, never validated; disabling just stops the requirement. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
🎉 ✨ Deploy Complete! 🚀🌐 ➡️ Open Review AppDeployment successful for PR #727, commit d29de5f 🎮 Control Plane Console |
PR #727 Review — Pro RSC Migration (base integration branch)OverviewThis is a well-scoped integration branch for migrating from the OSS The dependency upgrades from RC to stable and the three-bundle webpack pipeline are structurally sound. Observations below are mostly edge-case hardening and a few correctness concerns. Correctness
Security
Renderer password fallback and Reliability / PerformanceDocker
CI
Minor
|
Code Review: Pro RSC MigrationOverall this is a well-structured migration from react_on_rails OSS to react_on_rails_pro 16.6.0 with an RSC demo at /server-components. The sub-PR squash strategy produces a clean history. Observations focus on correctness, maintainability, and one security item. What works well
Issue 1 — RouterApp.server.jsx with use-client directive The use-client directive is correct from the RSC bundle's perspective (this is an SSR boundary, not an RSC server component). But the .server.jsx suffix means the opposite of the React community convention where .server.jsx signals an RSC Server Component. Consider renaming to RouterApp.ssr.jsx, or adding a prominent comment explaining why a .server.jsx file carries use-client. Issue 2 — Race condition in buildRetry (LiveActivityRefresher.jsx) The Retry handler calls setRefreshKey(newKey) then refetchComponent(...). setRefreshKey triggers the resetKeys prop on ErrorBoundary, resetting the boundary and re-mounting RSCRoute before refetchComponent resolves. The cache-priming intent is there, but the auto-reset fires from the state update — not after the await. RSCRoute may start a new fetch (cache miss) rather than hitting the pre-primed entry. Consider removing resetKeys from ErrorBoundary and relying solely on resetErrorBoundary() in .finally() to guarantee the reset happens after cache priming. Issue 3 — Dockerfile: no SIGTERM forwarding to children When the container receives SIGTERM (docker stop / ControlPlane scale-down), bash in a non-interactive script does not forward it to background children — they get SIGHUP at best or SIGKILL after the stop timeout. In-flight SSR requests are dropped without graceful drain. Acceptable for this tutorial, but worth a TODO comment. Graceful shutdown needs a trap handler forwarding the signal to child PIDs. Issue 4 — rscWebpackConfig.js: inconsistent return in the function-wrapper In the typeof rule.use === function branch, the code normalizes result into resultArray, then returns the original result (not resultArray) in the no-JS-loader path. No functional bug (webpack treats null/undefined and empty-array equivalently), but the asymmetry is a minor readability concern. Issue 5 — README title still references Rspack The heading reads ES2024, Rspack, Ruby on Rails Demo while the body correctly notes the temporary webpack switch. Either update the title to webpack for now, or drop the bundler name entirely to avoid churn on the flip-back. Issue 6 — sanitize-html allows http:// image sources allowedSchemes includes http, matching sanitize-html defaults, but http image URLs produce mixed-content warnings in HTTPS deployments. For a tutorial others copy from, restricting to https-only is safer. Summary: the race condition in the Retry flow and the .server.jsx naming inversion are the two items most worth revisiting before merge. The rest are low-severity or cosmetic. |
Greptile SummaryMigrates the tutorial from Confidence Score: 4/5Safe to merge; all findings are P2 and non-blocking. No P0 or P1 issues. The three P2 findings are: a cache-prime race in
Important Files Changed
Sequence DiagramsequenceDiagram
participant Browser
participant Rails
participant NodeRenderer
participant RSCRuntime
Note over Browser,RSCRuntime: Initial page load (/server-components)
Browser->>Rails: GET /server-components
Rails->>NodeRenderer: stream_view_containing_react_components (SSR)
NodeRenderer->>RSCRuntime: render ServerComponentsPage (RSC bundle)
RSCRuntime-->>NodeRenderer: Flight protocol stream
NodeRenderer-->>Rails: streamed HTML chunks
Rails-->>Browser: streamed HTTP response
Note over Browser,RSCRuntime: Client-driven RSC refresh (Refresh button)
Browser->>Rails: GET /rsc_payload/LiveActivity
Rails->>NodeRenderer: RSC payload generation
NodeRenderer->>RSCRuntime: render LiveActivity (rsc-bundle)
RSCRuntime-->>NodeRenderer: NDJSON chunks
NodeRenderer-->>Rails: NDJSON stream
Rails-->>Browser: NDJSON (application/x-ndjson)
Browser->>Browser: RSCRoute reconciles streamed payload
Note over Browser,RSCRuntime: Error boundary retry flow
Browser->>Browser: Simulate Error — ErrorBoundary catches
Browser->>Rails: refetchComponent (pre-warms cache)
Browser->>Browser: resetErrorBoundary — RSCRoute re-renders from cache
Reviews (1): Last reviewed commit: "Disable react/prop-types project-wide; d..." | Re-trigger Greptile |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d29de5f3c5
ℹ️ 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".
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
README.md (1)
76-93:⚠️ Potential issue | 🟡 MinorFix broken TOC anchors reported by markdownlint.
The table of contents includes fragments that don’t resolve (
#about-shakacode,#open-code-of-conduct), which triggers MD051.📝 Minimal fix
- + [About ShakaCode](`#about-shakacode`) + [RubyMine and WebStorm](`#rubymine-and-webstorm`) -+ [Open Code of Conduct](`#open-code-of-conduct`)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@README.md` around lines 76 - 93, The TOC contains links whose fragment identifiers don't match actual heading anchors (triggering MD051): update the table-of-contents entries for "#about-shakacode" and "#open-code-of-conduct" to match the real heading text/anchors (e.g., ensure they are exactly "#about-shakacode" or change them to the actual generated anchors like "#about-shakacode-1" or rename the headings to "About ShakaCode" and "Open Code of Conduct" so the fragments resolve), or alternatively remove/replace the broken entries so the markdown TOC fragments match the corresponding headings; verify other TOC entries (e.g., "Demoed Functionality", "Basic Demo Setup") still match their headings to satisfy markdownlint MD051.
🧹 Nitpick comments (1)
.controlplane/templates/rails.yml (1)
18-35: Raise the baseline, but watch resource pressure.Bumping the container to
1Gimakes sense with Rails and the co-located renderer in the same pod. Please keep an eye on OOM kills and CPU throttling after rollout, since CapacityAI can only adapt within the single-replica budget.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.controlplane/templates/rails.yml around lines 18 - 35, Keep the bumped baseline memory: 1Gi but explicitly monitor for OOM kills and CPU throttling after rollout; if you observe resource pressure, update defaultOptions.autoscaling.maxScale from 1 to allow multiple replicas or relax the single-replica constraint in defaultOptions, and/or tune defaultOptions.capacityAI thresholds so CapacityAI can increase CPU/memory within the replica before scaling; ensure alerts/metrics are in place to detect OOM and CPU throttling when memory: 1Gi is used.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.controlplane/Dockerfile:
- Around line 83-87: The current CMD starts renderer/node-renderer.js in
background and immediately starts Rails, then unconditionally exits 1 on any
child exit; change it so the renderer is started and the container waits for the
renderer to signal readiness (e.g., poll a renderer health endpoint or block on
a readiness file/socket) before launching bundle exec thrust bin/rails server,
and when either child exits use the actual exit code of that process rather than
exit 1; update the CMD/bash logic around renderer/node-renderer.js,
RENDERER_PID, RAILS_PID and the wait -n usage to capture and propagate the
child's exit status (exit $STATUS) and ensure the other process is
terminated/cleaned up.
In @.controlplane/templates/app.yml:
- Around line 37-40: The template currently hard-codes the environment variable
RSC_SUSPENSE_DEMO_DELAY to 'true'; change this so the demo delay is off by
default by removing the hard-coded 'true' assignment and either set
RSC_SUSPENSE_DEMO_DELAY to 'false' or omit the variable entirely from the shared
template, and instead document/override it in review-app specific overlays (so
only review apps enable the delay); update any comments to reflect that the
default is off and that review-apps must explicitly set
RSC_SUSPENSE_DEMO_DELAY='true' when needed.
In `@app/controllers/pages_controller.rb`:
- Around line 5-7: There is no blank line after the module inclusion which
triggers RuboCop Layout/EmptyLinesAfterModuleInclusion; update the
PagesController by inserting one empty line immediately after the include
ReactOnRailsPro::Stream statement so that the include line and the before_action
:set_comments (and any other subsequent declarations) are separated by a blank
line.
In `@client/app/bundles/server-components/components/TogglePanel.jsx`:
- Around line 5-29: Convert the arrow component to a function declaration
(change const TogglePanel = (...) => to function TogglePanel({ title, children
}) { ... }) to satisfy the react/function-component-definition rule; keep the
isOpen and setIsOpen state but also call React's useId (or accept an id prop) to
create a stable id for the collapsible region, then add accessibility
attributes: on the toggle button add aria-expanded={isOpen} and
aria-controls={contentId}, and give the content container the matching
id={contentId} (and include role="region" or aria-labelledby if desired);
reference the TogglePanel function, isOpen, setIsOpen, and useId/contentId when
making these changes.
In `@client/app/packs/stores-registration.js`:
- Around line 1-4: Move the explanatory comment so the top-of-file directive
stays first: leave the "'use client';" directive as the very first statement,
then add a single blank line and place the existing comment immediately after it
(above the import ReactOnRails line). This keeps "'use client'" at the top while
satisfying the lines-around-directive requirement by not putting any comment
before the directive.
In `@config/initializers/react_on_rails_pro.rb`:
- Line 18: The current assignment to config.renderer_password uses a hardcoded
fallback which is unsafe; change it to require an explicit value in
non-development/test environments by removing the default and raising if
ENV["RENDERER_PASSWORD"] is blank (e.g. set config.renderer_password =
ENV["RENDERER_PASSWORD"].presence and, for production (use Rails.env.production?
or !Rails.env.development? && !Rails.env.test?), raise a clear error like
"RENDERER_PASSWORD must be set in production" when blank), or alternatively load
from Rails credentials for production and still raise if neither is present;
update the code around config.renderer_password to implement this check.
In `@config/webpack/rscWebpackConfig.js`:
- Around line 16-35: The current rules.forEach mutates rule.use and uses a
nested ternary; instead produce immutable updates: replace the forEach with a
map over rules to build a new array (e.g., const newRules = rules.map(rule => {
... })) and return a newRule object for each input rule rather than reassigning
properties. For the function branch (identify originalUse and rscLoaderWrapper)
create a wrappedUse function (const wrappedUse = function(data) { const result =
originalUse.call(this, data); const resultArray = Array.isArray(result) ? result
: result ? [result] : []; if (extractLoader({ use: resultArray },
'babel-loader') || extractLoader({ use: resultArray }, 'swc-loader')) { return
[...resultArray, { loader: 'react-on-rails-rsc/WebpackLoader' }]; } return
result; }); then return { ...rule, use: wrappedUse }; for the array branch,
return { ...rule, use: [...rule.use, { loader:
'react-on-rails-rsc/WebpackLoader' }] } and keep other rules unchanged; this
removes in-place param reassignment and replaces the nested ternary with
explicit branching while still using extractLoader to detect babel/swc loaders.
---
Outside diff comments:
In `@README.md`:
- Around line 76-93: The TOC contains links whose fragment identifiers don't
match actual heading anchors (triggering MD051): update the table-of-contents
entries for "#about-shakacode" and "#open-code-of-conduct" to match the real
heading text/anchors (e.g., ensure they are exactly "#about-shakacode" or change
them to the actual generated anchors like "#about-shakacode-1" or rename the
headings to "About ShakaCode" and "Open Code of Conduct" so the fragments
resolve), or alternatively remove/replace the broken entries so the markdown TOC
fragments match the corresponding headings; verify other TOC entries (e.g.,
"Demoed Functionality", "Basic Demo Setup") still match their headings to
satisfy markdownlint MD051.
---
Nitpick comments:
In @.controlplane/templates/rails.yml:
- Around line 18-35: Keep the bumped baseline memory: 1Gi but explicitly monitor
for OOM kills and CPU throttling after rollout; if you observe resource
pressure, update defaultOptions.autoscaling.maxScale from 1 to allow multiple
replicas or relax the single-replica constraint in defaultOptions, and/or tune
defaultOptions.capacityAI thresholds so CapacityAI can increase CPU/memory
within the replica before scaling; ensure alerts/metrics are in place to detect
OOM and CPU throttling when memory: 1Gi is used.
🪄 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: 5308d56a-d8eb-4aac-9bc2-c6896aa83eaa
⛔ Files ignored due to path filters (2)
Gemfile.lockis excluded by!**/*.lockyarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (47)
.controlplane/Dockerfile.controlplane/templates/app.yml.controlplane/templates/org.yml.controlplane/templates/rails.yml.env.example.eslintrc.github/workflows/rspec_test.yml.gitignoreGemfileProcfile.devREADME.mdapp/controllers/pages_controller.rbapp/views/pages/server_components.html.erbclient/__tests__/webpack/bundlerUtils.spec.jsclient/app/bundles/comments/components/Footer/ror_components/Footer.jsxclient/app/bundles/comments/components/NavigationBar/NavigationBar.jsxclient/app/bundles/comments/components/SimpleCommentScreen/ror_components/SimpleCommentScreen.jsxclient/app/bundles/comments/constants/paths.jsclient/app/bundles/comments/rescript/ReScriptShow/ror_components/RescriptShow.jsxclient/app/bundles/comments/startup/App/ror_components/App.jsxclient/app/bundles/comments/startup/ClientRouterAppExpress.jsxclient/app/bundles/comments/startup/NavigationBarApp/ror_components/NavigationBarApp.jsxclient/app/bundles/comments/startup/RouterApp/ror_components/RouterApp.client.jsxclient/app/bundles/comments/startup/RouterApp/ror_components/RouterApp.server.jsxclient/app/bundles/comments/startup/serverRegistration.jsxclient/app/bundles/server-components/components/CommentsFeed.jsxclient/app/bundles/server-components/components/LiveActivityRefresher.jsxclient/app/bundles/server-components/components/ServerInfo.jsxclient/app/bundles/server-components/components/TogglePanel.jsxclient/app/bundles/server-components/ror_components/LiveActivity.jsxclient/app/bundles/server-components/ror_components/ServerComponentsPage.jsxclient/app/libs/requestsManager.jsclient/app/packs/stimulus-bundle.jsclient/app/packs/stores-registration.jsconfig/initializers/react_on_rails_pro.rbconfig/routes.rbconfig/shakapacker.ymlconfig/webpack/bundlerUtils.jsconfig/webpack/clientWebpackConfig.jsconfig/webpack/commonWebpackConfig.jsconfig/webpack/rscWebpackConfig.jsconfig/webpack/serverWebpackConfig.jsconfig/webpack/webpackConfig.jspackage.jsonrenderer/node-renderer.jsspec/requests/server_components_spec.rbspec/system/server_components_demo_spec.rb
|
✅ Review app for PR #727 was successfully deleted |
Migrates this tutorial to React on Rails Pro with the Pro Node Renderer for SSR, and adds a React Server Components demo at
/server-components. RSC is a Pro-only feature (enable_rsc_supportdoesn't exist in the OSS gem), so demonstrating RSC in this tutorial inherently moves the whole app to Pro — that's the entry ticket, not scope drift.What changes
Dependencies. Gem and npm move from
react_on_railstoreact_on_rails_pro16.6.0. React pinned to~19.0.4(RSC's minimum). Addsreact-on-rails-pro-node-rendererandreact-on-rails-rsc.SSR runtime. Server-side rendering moves from ExecJS to the Pro Node Renderer (
renderer/node-renderer.js, port 3800). The renderer wiresadditionalContext: { URL, AbortController }forreact-router-dom,stubTimers: falseso React's RSC server renderer can usesetTimeoutfor Flight-protocol yielding, andreplayServerAsyncOperationLogs: trueto surface async-server-component console output back throughconsoleReplayScript.RSC demo. A new page at
/server-componentsexercises React Server Components end-to-end: server-only library imports (Nodeos+lodash) that never reach the browser, async server components streamed via<Suspense>, the donut pattern (a'use client'component nested in a server tree), and client-driven server-component re-fetching withreact-error-boundary+useRSC().refetchComponent. See the React Server Components section in the README for the architecture.Verification
spec/requests/server_components_spec.rb,spec/system/server_components_demo_spec.rb).Follow-ups (post-merge)
config/shakapacker.ymlback to rspack once shakacode/react_on_rails_rsc#29 ships native rspack support for the RSC plugin. ATODOinconfig/shakapacker.ymltracks the flip.CLAUDE.mdandAGENTS.mdupdates documenting the new architecture for future agents — tracked in Comprehensive CLAUDE.md and AGENTS.md update #730.Stack history
Three sub-PRs squash-merged into this base:
react_on_rails_pro.The webpack switch in Sub-PR 2 is a tactical reversal of #702 (which moved this tutorial to rspack);
react-on-rails-rsc/WebpackPluginrequires webpack APIs that rspack 2.0.0-beta.7's compatibility layer doesn't cover yet.Summary by CodeRabbit
Release Notes
New Features
Dependencies
Infrastructure