Skip to content

Commit 823aa66

Browse files
committed
Merge remote-tracking branch 'origin/main' into jg/test-concurrent-ports
* origin/main: docs: clarify extensible precompile ownership (#3226) docs: fix broken Empirical-Core GitHub link in PROJECTS.md (#3326) docs(planning): PPR implementation plan for React on Rails (#3323) docs: plan React 19 partial pre-rendering work (#3237) Relax Pro jwt dependency to allow 3.2.0 security fix docs(planning): plan RSCRoute ssr=false implementation (#3317) docs: Add PPR investigation findings (#3314) feat(pro): Async props & incremental RSC rendering + length-prefixed streaming protocol (#2903) fix: stale references and lost Gemfile pins after Pro migration (#3104) Restore Metrics/ModuleLength limit to 180 Clarify RuboCop lint contract Exclude dead project URLs from markdown link check (#3306) Canonicalize GitHub links in docs Reduce `react-on-rails-pro-node-renderer` package size (#3304) fix: format stray markdown files and correct broken link breaking CI on main (#3301) docs(planning): JSON render-request body migration plan (#3280) (#3299) docs: experiment plan for decoupling props from JS source (#3281) (#3297) docs: memory leakage investigation report for SSR pipeline (#3296) docs: fix MDX-incompatible patterns blocking sc-website docs sync (#3275)
2 parents 94ab206 + 600af78 commit 823aa66

137 files changed

Lines changed: 12000 additions & 1482 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.lychee.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ exclude = [
4141
'^https://your-shared-addr\.c9users\.io',
4242
'^https://hichee\.com',
4343
'^https?://rubyonrails\.org/doctrine', # DNS issues
44+
'^https://www\.oppenheimerfunds\.com/?$', # DNS no longer resolves
4445

4546
# ============================================================================
4647
# SSL CERTIFICATE ISSUES
@@ -93,6 +94,7 @@ exclude = [
9394
'^https://(www\.)?guavapass\.com', # TLS handshake failures from CI
9495
'^https?://(www\.)?hvmn\.com', # Returns 503 from CI
9596
'^https://(www\.)?hawaiichee\.com/?$', # Intermittent 500 from CI
97+
'^https://deliveroo\.co\.uk/?$', # Returns 403 for automated requests
9698

9799
# ============================================================================
98100
# EXTERNAL SITES THAT TIMEOUT DURING CI CHECKS
@@ -114,9 +116,12 @@ exclude = [
114116
# return 5xx responses to lychee in CI, even when the pages are live.
115117
'^https://github\.com/shakacode/react_on_rails/compare/',
116118
'^https://github\.com/shakacode/react_on_rails/pull/[0-9]+$', # Intermittent 5xx from GitHub PR pages in CI
119+
'^https://github\.com/shakacode/react_on_rails/actions/workflows/pro-test-package-and-gem\.yml$', # Intermittent 502 from GitHub Actions workflow pages in CI
117120
'^https://github\.com/shakacode/react_on_rails/issues/2766$', # Intermittent 502 from GitHub issue pages in CI
118121
'^https://github\.com/rack/rack/blob/main/SPEC\.rdoc$', # Intermittent 502 from GitHub blob pages in CI
119122
'^https://github\.com/shakacode/shakapacker/blob/cdf32835d3e0949952b8b4b53063807f714f9b24/package/environments/base\.js(#.*)?$', # Intermittent 502 from GitHub blob view in CI
123+
'^https://github\.com/npm/cli/issues/6253$', # Intermittent 502 from GitHub issue pages in CI
124+
'^https://github\.com/search\?q=gem\+react_on_rails&ref=advsearch&type=repositories&utf8=%E2%9C%93$', # Intermittent 502 from GitHub search in CI
120125

121126
# ============================================================================
122127
# EXTERNAL DOC PAGES WITH CI CONNECTIVITY FAILURES

.rubocop.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ AllCops:
2828
- 'node_modules/**/*'
2929
- 'spec/fixtures/**/*'
3030
- 'spec/react_on_rails/dummy-for-generators/**/*'
31+
# Root-wide RuboCop uses root-relative paths. Keep generated/package-specific
32+
# exclusions here so the root sweep does not fail on files package CI exempts.
33+
- 'react_on_rails_pro/spec/dummy/db/schema.rb'
34+
- 'react_on_rails_pro/spec/dummy/db/seeds.rb'
3135
- '**/vendor/**/*'
3236

3337
Naming/FileName:
@@ -74,7 +78,7 @@ Metrics/MethodLength:
7478
Max: 41
7579

7680
Metrics/ModuleLength:
77-
Max: 200
81+
Max: 180
7882

7983
RSpec/DescribeClass:
8084
Enabled: false

AGENTS.md

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,15 @@ bundle && pnpm install
3333
pnpm run build
3434

3535
# Lint (MANDATORY before every commit)
36-
bundle exec rubocop # Ruby — must pass with zero offenses
37-
pnpm run lint # JS/TS via ESLint
38-
pnpm start format.listDifferent # Check Prettier formatting
39-
rake lint # All linting (Ruby + JS + formatting)
36+
(cd react_on_rails && bundle exec rubocop) # OSS Ruby lint — CI-equivalent
37+
# Pro Ruby lint — CI-equivalent when Pro files or RuboCop config change
38+
(cd react_on_rails_pro && bundle exec rubocop --ignore-parent-exclusion)
39+
pnpm run lint # JS/TS via ESLint
40+
pnpm start format.listDifferent # Check Prettier formatting
41+
rake lint # All linting (Ruby + JS + formatting)
42+
43+
# Optional Ruby diagnostic from the repo root (not the CI contract)
44+
bundle exec rubocop
4045

4146
# Auto-fix formatting
4247
rake autofix # Preferred for all formatting
@@ -177,7 +182,13 @@ For small, focused PRs (roughly 5 files changed or fewer and one clear purpose):
177182

178183
### Always
179184

180-
- Run `bundle exec rubocop` before committing — CI will reject violations
185+
- Run the CI-equivalent Ruby lint before committing, not the root `bundle exec rubocop`:
186+
```bash
187+
(cd react_on_rails && bundle exec rubocop)
188+
# Also run when touching Pro Ruby or RuboCop config:
189+
(cd react_on_rails_pro && bundle exec rubocop --ignore-parent-exclusion)
190+
```
191+
Root `bundle exec rubocop` is a broad local sweep, not the CI contract.
181192
- Use `pnpm` for all JS operations — never `npm` or `yarn`
182193
- Use `bundle exec` for Ruby commands
183194
- Ensure all files end with a newline

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,19 @@ After a release, run `/update-changelog` in Claude Code to analyze commits, writ
3636
#### Added
3737

3838
- **[Pro]** **Pre-seed renderer cache for Docker builds**: New `react_on_rails_pro:pre_seed_renderer_cache` rake task copies compiled server bundles into the Node Renderer's bundle-hash cache directory structure during Docker image builds, eliminating the 410→retry cold-start latency (200ms–1s+) on the first SSR request after deployment. Supports `RENDERER_SERVER_BUNDLE_CACHE_PATH`, RSC bundles, and rolling-deploy guidance centered on current and previous bundle hashes. The legacy `pre_stage_bundle_for_node_renderer` task now stages the same cache layout via symlinks for same-filesystem workflows. **Note:** `RENDERER_BUNDLE_PATH` is now deprecated in favor of `RENDERER_SERVER_BUNDLE_CACHE_PATH` across both tasks. Existing users with `RENDERER_BUNDLE_PATH` set will see a deprecation warning on stderr. [PR 3124](https://github.com/shakacode/react_on_rails/pull/3124) by [justin808](https://github.com/justin808).
39+
- **[Pro]** **Async props with incremental React Server Component rendering**: Added the `stream_react_component_with_async_props` and `rsc_payload_react_component_with_async_props` view helpers, which accept a block to declare props that are fetched concurrently and streamed to the rendering component as each value becomes available. The React component renders its shell immediately (with `<Suspense>` fallbacks) and progressively re-renders as async prop promises resolve, dramatically improving Time to First Byte for pages with slow data fetches. Components access async props through the `getReactOnRailsAsyncProp` function injected into props (typed via the new `WithAsyncProps` TypeScript helper). Requires `config.enable_rsc_support = true`. This is an additive feature — existing `stream_react_component` and `rsc_payload_react_component` calls are unaffected. [PR 2903](https://github.com/shakacode/react_on_rails/pull/2903) by [AbanoubGhadban](https://github.com/AbanoubGhadban).
3940

4041
#### Changed
4142

4243
- **[Pro]** **Unified renderer cache staging**: `ReactOnRailsPro::PreSeedRendererCache.call(mode: :copy | :symlink)` is now the single entry point for staging the Node Renderer cache. Both modes produce the same `<cache>/<bundleHash>/<bundleHash>.js` layout. The `react_on_rails_pro:pre_seed_renderer_cache` rake task accepts `MODE=copy` (default; Docker/image builds) or `MODE=symlink` (same-filesystem). The auto-invocation at the end of `assets:precompile` defaults to `:symlink` (preserving prior behavior) and now honors `ASSETS_PRECOMPILE_RENDERER_CACHE_MODE=copy|symlink` so Docker builds that run `rake assets:precompile` as the final asset step can opt into copy mode without invoking the rake task separately. `MODE=copy` raises a clear error when neither `RENDERER_SERVER_BUNDLE_CACHE_PATH` nor `RENDERER_BUNDLE_PATH` is set in non-dev/test environments, because the Node renderer's default lookup can differ from the Ruby side and would silently drop pre-seeded bundles in the wrong directory. The legacy `react_on_rails_pro:pre_stage_bundle_for_node_renderer` task and `ReactOnRailsPro::PrepareNodeRenderBundles` class remain as deprecated shims that emit a once-per-process warning and delegate to `mode: :symlink`. `react_on_rails:doctor` flags deploy scripts that still reference the deprecated task. **Heads-up for custom scripts:** the previous flat layout wrote `$RENDERER_BUNDLE_PATH/<renderer_bundle_file_name>`; any external scripts (health checks, renderer launchers) that read that path directly must now read `$RENDERER_SERVER_BUNDLE_CACHE_PATH/<bundleHash>/<bundleHash>.js` instead. [PR 3124](https://github.com/shakacode/react_on_rails/pull/3124) by [justin808](https://github.com/justin808).
4344
- **[Pro]** **Pro generator now creates the Node Renderer at `renderer/node-renderer.js`**: The canonical location for the Node Renderer entry point is now a dedicated top-level `renderer/` directory instead of `client/`, making it straightforward to exclude from production Docker builds that strip JS sources after bundling. Docs and Pro `spec/dummy` now use the new path consistently. Existing apps are unaffected — the generator skips files that already exist (including a legacy `client/node-renderer.js`). Fixes [Issue 3073](https://github.com/shakacode/react_on_rails/issues/3073). [PR 3165](https://github.com/shakacode/react_on_rails/pull/3165) by [justin808](https://github.com/justin808).
4445
- **[Pro] Documentation standardized on `REACT_RENDERER_URL` env var name**: The configuration example in `docs/oss/configuration/configuration-pro.md` now shows `ENV["REACT_RENDERER_URL"]` instead of the older `ENV["RENDERER_URL"]`, aligning with the rest of the docs and the generator template. Existing apps that read `ENV["RENDERER_URL"]` in their initializer continue to work — the Pro `renderer_url` config is whichever env var the user reads in their initializer; no gem code reads either name directly. Rename the env var in your infrastructure configs (and update the initializer to match) if you want to align with the new convention. `bin/dev` now also warns when `RENDERER_URL` is set without `REACT_RENDERER_URL` so the rename doesn't silently fall back to the default renderer URL. [PR 3142](https://github.com/shakacode/react_on_rails/pull/3142) by [justin808](https://github.com/justin808).
46+
- **Length-prefixed streaming wire protocol**: The internal protocol between the Rails gems and the Node renderer (and the in-process bundle for the OSS non-streaming path) now uses a length-prefixed framing — `<metadata JSON>\t<content byte length hex>\n<raw content bytes>` — instead of wrapping every HTML chunk in a JSON envelope, eliminating ~30% serialize/escape overhead on streamed HTML and correctly handling multibyte content and chunk boundaries. This is an internal transport detail: React on Rails always ships the `react_on_rails`/`react_on_rails_pro` gems, the `react-on-rails`/`react-on-rails-pro` npm packages, and the `react-on-rails-pro-node-renderer` as a matched version set, and the Ruby parser also auto-detects the legacy JSON format, so no application action is required when upgrading all artifacts together. [PR 2903](https://github.com/shakacode/react_on_rails/pull/2903) by [AbanoubGhadban](https://github.com/AbanoubGhadban).
4547

4648
#### Fixed
4749

50+
- **[Pro]** **Allow patched ruby-jwt releases**: React on Rails Pro now requires `jwt >= 3.2.0`, removing the previous `~> 2.7` cap so applications can resolve the patched ruby-jwt release for the empty-key HMAC advisory. [PR 3322](https://github.com/shakacode/react_on_rails/pull/3322) by [ihabadham](https://github.com/ihabadham).
51+
- **[Pro]** **Pro migration generator rewrites all base-package references and preserves Gemfile pins**: `rails generate react_on_rails:pro` now rewrites Jest/Vitest mock helpers (`jest.mock`, `vi.mock`, `requireActual`/`importActual`, and the rest) and TypeScript `declare module 'react-on-rails'` blocks alongside its existing `import`/`require`/dynamic-import handling, and the Gemfile swap now preserves the user's existing version pin (and other gem options) instead of overwriting them with the running gem's version. `react_on_rails:doctor` is widened to match: it also flags stale side-effect imports (`import 'react-on-rails';`), Jest/Vitest mock helpers, and `declare module` blocks, and the new side-effect-import pattern keeps the doctor a superset of the rewriter so anything the rewriter doesn't reach gets surfaced. Closes [Issue 3104](https://github.com/shakacode/react_on_rails/issues/3104). [PR 3232](https://github.com/shakacode/react_on_rails/pull/3232) by [justin808](https://github.com/justin808).
4852
- **Doctor now honors nested JavaScript package roots**: `react_on_rails:doctor` now checks package-manager lockfiles, `package.json`, and installed React from the configured `node_modules_location`, reducing false diagnostics for legacy apps that keep dependencies under `client/`. The Vite migration guide now documents the supported thin-wrapper pattern for those layouts. Note: a missing `package.json` at the configured `node_modules_location` now emits a warning instead of being silently skipped, so apps misconfigured against a nonexistent path will see new diagnostics on upgrade. Fixes [Issue 3205](https://github.com/shakacode/react_on_rails/issues/3205). [PR 3220](https://github.com/shakacode/react_on_rails/pull/3220) by [justin808](https://github.com/justin808).
4953
- **Generated pack regeneration is now serialized**: `generate_packs_if_stale` now uses a Rails `tmp/` lock file, re-checks staleness after waiting, and avoids concurrent cleanup/regeneration races when multiple processes trigger auto-bundling at the same time. Fixes [Issue 1627](https://github.com/shakacode/react_on_rails/issues/1627). [PR 3231](https://github.com/shakacode/react_on_rails/pull/3231) by [justin808](https://github.com/justin808).
5054
- **Install generator validates the selected JavaScript package manager**: The install generator now checks the manager selected from `REACT_ON_RAILS_PACKAGE_MANAGER`, the `packageManager` field in `package.json`, or a lockfile on disk — instead of passing when any JavaScript package manager is installed. When the selected command is missing, the error names the selected manager, the source that selected it, and the available alternatives. The generator also warns when `REACT_ON_RAILS_PACKAGE_MANAGER` is set to a value outside the supported set (`npm`, `pnpm`, `yarn`, `bun`). Addresses package manager validation from [Issue 1958](https://github.com/shakacode/react_on_rails/issues/1958). [PR 3229](https://github.com/shakacode/react_on_rails/pull/3229) by [justin808](https://github.com/justin808).
@@ -55,6 +59,10 @@ After a release, run `/update-changelog` in Claude Code to analyze commits, writ
5559
- **Client startup now recovers if initialization begins during `interactive` after `DOMContentLoaded` already fired**: React on Rails now still initializes the page when the client bundle starts in the browser timing window after `DOMContentLoaded` but before the document reaches `complete`. Fixes [Issue 3150](https://github.com/shakacode/react_on_rails/issues/3150). [PR 3151](https://github.com/shakacode/react_on_rails/pull/3151) by [ihabadham](https://github.com/ihabadham).
5660
- **Doctor accepts TypeScript server bundle entrypoints**: `react_on_rails:doctor` now resolves common source entrypoint suffixes (`.js`, `.jsx`, `.ts`, `.tsx`, `.mjs`, `.cjs`) before warning that the server bundle is missing, preventing false positives when apps use `server-bundle.ts`. [PR 3111](https://github.com/shakacode/react_on_rails/pull/3111) by [justin808](https://github.com/justin808).
5761
- **Doctor no longer fails custom projects for a missing generated `bin/dev`**: `react_on_rails:doctor` now downgrades a missing official React on Rails `bin/dev` launcher from an error to a warning and adds explicit guidance when a custom `./dev` script is detected, so custom projects can pass diagnostics when their development setup is intentional. Fixes [Issue 3103](https://github.com/shakacode/react_on_rails/issues/3103). [PR 3117](https://github.com/shakacode/react_on_rails/pull/3117) by [justin808](https://github.com/justin808).
62+
- **[Pro]** **Reduced `react-on-rails-pro-node-renderer` published package size**: added a `files` whitelist to `package.json` so `pnpm pack` no longer includes `src/`, `tests/` fixtures, `*.map`, and `lib/tsconfig.tsbuildinfo` — matching the convention used by the sibling packages. Also marked `react_on_rails_pro/spec/dummy` as `private` so it can never be accidentally published. [PR 3304](https://github.com/shakacode/react_on_rails/pull/3304) by [alexeyr-ci2](https://github.com/alexeyr-ci2).
63+
- **[Pro]** **HTTPX bidirectional streaming reliability**: Fixed streaming request timeouts when using HTTPX with both the `:stream` and `:stream_bidi` plugins. The request now uses the `build_request` pattern with an explicit `request.close` so the HTTP/2 `END_STREAM` flag is sent, and a temporary monkey-patch (`httpx_stream_bidi_patch.rb`) works around an upstream `:stream_bidi` retry bug that left stale body callbacks registered and crashed retried requests with `protocol_error`. The patch is scoped and will be removed once fixed upstream. [PR 2903](https://github.com/shakacode/react_on_rails/pull/2903) by [AbanoubGhadban](https://github.com/AbanoubGhadban).
64+
- **[Pro]** **Progressive RSC streaming flush granularity**: RSC streaming now flushes on React's per-render-cycle `flush()` signal instead of `setTimeout(flush, 0)`, so the shell and each resolved `<Suspense>` boundary stream as separate chunks rather than being merged into one large first message. This restores progressive streaming (and fixes worse-than-SSR First Contentful Paint) on pages with fast queries, and eliminates partial-HTML-tag chunks. Fixes [Issue 3194](https://github.com/shakacode/react_on_rails/issues/3194). [PR 2903](https://github.com/shakacode/react_on_rails/pull/2903) by [AbanoubGhadban](https://github.com/AbanoubGhadban).
65+
- **[Pro]** **Node renderer graceful shutdown after stream timeouts**: Fixed workers taking 30+ seconds to shut down after a `StreamChunkTimeoutError` during streaming. `handleGracefulShutdown` now also decrements the active-request count on `onRequestAbort`/`onTimeout`, the `PassThrough` wrapper is destroyed when the source render stream errors, and the HTTP response is closed on chunk timeout so connections to Rails no longer hang. Fixes [Issue 2270](https://github.com/shakacode/react_on_rails/issues/2270) and [Issue 2308](https://github.com/shakacode/react_on_rails/issues/2308). [PR 2903](https://github.com/shakacode/react_on_rails/pull/2903) by [AbanoubGhadban](https://github.com/AbanoubGhadban).
5866

5967
#### Changed
6068

CONTRIBUTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ It's critical to configure your IDE/editor to ignore certain directories. Otherw
5454

5555
# Example apps
5656

57-
The [`react_on_rails/spec/dummy` app](https://github.com/shakacode/react_on_rails/blob/main/react_on_rails/spec/dummy) is an example of the various setup techniques you can use with the gem.
57+
The [`react_on_rails/spec/dummy` app](https://github.com/shakacode/react_on_rails/tree/main/react_on_rails/spec/dummy) is an example of the various setup techniques you can use with the gem.
5858

59-
There are also two such apps for React on Rails Pro: [one using the Node renderer](https://github.com/shakacode/react_on_rails/blob/main/react_on_rails_pro/spec/dummy) and [one using ExecJS](https://github.com/shakacode/react_on_rails/blob/main/react_on_rails_pro/spec/execjs-compatible-dummy).
59+
There are also two such apps for React on Rails Pro: [one using the Node renderer](https://github.com/shakacode/react_on_rails/tree/main/react_on_rails_pro/spec/dummy) and [one using ExecJS](https://github.com/shakacode/react_on_rails/tree/main/react_on_rails_pro/spec/execjs-compatible-dummy).
6060

6161
When you add a new feature, consider adding an example demonstrating it to the example apps.
6262

Gemfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
# Delegates to the react_on_rails gem for development
55

66
# Use the open-source gem's Gemfile
7-
eval_gemfile File.expand_path('react_on_rails/Gemfile', __dir__)
7+
eval_gemfile File.expand_path("react_on_rails/Gemfile", __dir__)

PROJECTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ _Please support the project by [emailing Justin Gordon](mailto:justin@shakacode.
3737

3838
## Open Source Projects Live
3939

40-
- [github.com/empirical-org/Empirical-Core](https://github.com/empirical-org/Empirical-Core): [Quill.org](https://quill.org/) Provides free tools to make your students better writers.
40+
- **[Quill.org](https://quill.org/)**: Provides free tools to make your students better writers.
4141
- **[Coderwall](https://coderwall.com/)**: The latest development and design tips, tools, and projects from our developer community. Source at [github.com/coderwall/coderwall-next](https://github.com/coderwall/coderwall-next)
4242

4343
## Demos and Tutorials

0 commit comments

Comments
 (0)