Added
- [Pro] React 19.3 support with
react-on-rails-rsc@19.3.1-rc.0: The node renderer startup
check andreact_on_rails:doctornow acceptreact-on-rails-rsc19.3.1-rc.0 (npmnext) and later
19.3.1+ releases with React/React DOM 19.3.x. Thereact-on-rails-prooptional peer range admits
~19.3.1-rc.0. Publishedreact-on-rails-rsc19.3.0 (npmlatest, which bundles React 19.2.8 Flight)
stays supported with React 19.2.8+ only, and 19.2.x stays on React 19.2.7+. Each check rejects mixed pairs
such as 19.3.0 with React 19.3 or 19.2.x with React 19.3, and it rejects prerelease React builds. Doctor's
Fix command names the React version that matches the installed RSC package. The generator still installs
stablereact-on-rails-rsc19.2.1. Action required for upgraders: apps generated by 17.1.0 pin the
supersededreact-on-rails-rsc19.3.0-rc.4, which the node renderer now refuses at startup. Install 19.3.0
with React/React DOM 19.2.8+, or 19.3.1-rc.0 with React/React DOM 19.3.0. The node renderer error and Doctor's Fix name the stable
release to install for the known 19.3.0 transition; other rejected prereleases no longer imply that
a compatible stable version is published. Doctor falls back to the published generator pin for those versions.
#5094 by
justin808.
Fixed
-
[Pro] RSC streams no longer encode consumer logs into the Flight payload: React 19.3 development
Flight keeps a console hook active while it flushes chunks, so logs from code consuming the returned RSC
Readable were serialized as:W["log"...]rows. In non-production builds, RSC payload streams now use
Node's native console while chunks reach the consumer. Production builds and HTML streams keep the
caller's console unchanged. #5094 by
justin808. -
authenticityHeaders()no longer mutates its input object: The helper now returns a new merged object instead of writing CSRF headers into the caller'sotherHeadersargument. Previously, passing a shared or module-level headers object would bake a stale CSRF token into it, causing intermittent422 InvalidAuthenticityTokenerrors after Turbo navigations. Fixes #5028. -
Version checking now resolves the installed package version from pnpm, bun, and Yarn Berry lockfiles:
pnpm-lock.yaml(lockfileVersion 5.4/6.0/9.0, including the pnpm 11 multi-document form),bun.lock
(lockfileVersion 0-2, JSONC), and Yarn Berryyarn.lock(__metadataversions 4-8) join the existing Yarn
classic andpackage-lock.jsonsupport, with the same package.json fallback and precedence behavior as before —
so semver ranges like^17.0.0in package.json no longer fail Rails boot for pnpm, bun, and Yarn 2+ users.
The binarybun.lockbis not parsed — migrate with
bun install --save-text-lockfile --frozen-lockfile --lockfile-only, or pin the exact version. A malformed or
unreadable lockfile falls back to the package.json version instead of erroring, and the non-exact-version error
text now explains the relaxed installed-version rule instead of forbidding semver ranges outright. Fixes
#5049.
#5058 by
AbanoubGhadban. -
bin/dev killnow verifies every app-scoped Overmind endpoint within a bounded control budget:
shutdown discovers alltmp/sockets/overmind*.sockendpoints, fails closed when discovery or probing cannot be
completed, and terminates and reaps timed-out control clients under one shared deadline per phase. When no renderer
port is exported, recognized generated Procfiles supply the local Node Renderer fallback without widening the kill
scope to an unrelated listener on port 3800. Fixes
#4944. #4986 by
justin808. -
Ruby type signatures now match supported helper inputs: RBS accepts the compatibility
immediate_hydration:option onController#redux_store, Pathname locale directories,
Hash prerender props, and asset-checker/compiler injection keywords. Package-manager detection
declares its actual symbol results, while invalid inputs remain rejected by runtime type checks.
Fixes #5037. -
Configuration RBS signatures match supported values: Runtime type checking now accepts the documented
Pathnamedirectories, unset configuration values, and thecheck_database_on_dev_startkeyword without changing
runtime behavior. Fixes #5036
by justin808. -
[Pro]
TieredCacheHandlerkeeps serving L1 hits for entries older thanl1MaxTtlSeconds: Promoting an
L2 hit into L1 rewrote the entry'srevalidatebut kept its originaltimestamp, so any entry older than
l1MaxTtlSecondswas written to L1 already expired — permanently bypassing L1 for that key and paying an L2
(e.g. Redis) round trip on every request. Promoted entries now expire at the earlier of the original entry's
expiry andl1MaxTtlSecondsfrom promotion time, and entries with no remaining lifetime skip the L1 write.
A non-positivel1MaxTtlSecondsnow disables L1 entirely instead of storing permanent L1 entries.
Action required for upgraders: if you run a persistent L1 (e.g.RedisCacheHandler) with a
non-positivel1MaxTtlSeconds, the disabled L1 retains entries written before it was disabled —
flush that L1 store before re-enabling it with a positive cap, or retained stale/indefinite entries
become readable again.
With the default in-memory L1 this was purely a performance bug; aRedisCacheHandlerL1 could
additionally serve entries past their intended expiry, because Redis applies TTLs at write time —
promoted entries now always encode exactly the remaining lifetime. Fixes
#5027.
#5029 by
AbanoubGhadban. -
Console replay can no longer swallow the rest of the page, and replayed messages are no longer altered:
A server-sideconsole.logargument containing<!--could switch the browser's HTML parser into a state where
the console-replay<script>consumed the remainder of the document. The replay code is now escaped with the same
lossless two-part escape used by Pro's RSC payload injection (<!--→<\!--,</script→</\script), shared
as one helper across core and Pro, so the browser console now receives the original logged text (previously
</scriptwas rewritten to(/script). Fixes
#5034.
#5035 by
AbanoubGhadban. -
[Pro] Standalone upgrades preserve customized bundler configurations: The Pro generator automatically
upgrades only complete, unchanged configuration pairs from supported current templates. Customized, historical,
missing, or ambiguous pairs remain unchanged with manual migration instructions, preventing helper redeclarations
and partial upgrades. Fixes #4789.
#5010 by justin808. -
RSC agent guardrail upgrades now preserve complete files and clean up their stale hook groups:
The installer atomically replaces copied skill and hook files while preserving their existing permission behavior,
and removesPostToolUsegroups only when removing a managed guardrail hook leaves the group empty. Unrelated
Claude settings and hook groups remain unchanged. Addresses items 1 and 2 of
#4815. -
Explicit
id: nilnow uses the configured automatic DOM id behavior: Component rendering no longer omits the
container id while still reporting that a random id is active. Fixes
#4993.
#4998 by
justin808. -
bin/dev killnow stops only the current app directory's processes, and verifies they are
gone: the kill path matched command lines machine-wide (pgrep -f rails,pgrep -f overmind,
pgrep -f ruby.*puma, and friends) and scanned ports with an unfilteredlsof -ti :PORT, so
running it in one worktree could terminate a Rails server, a Webpack dev server, or an unrelated
client connection belonging to a different checkout — then printed "All processes terminated"
without checking that anything had actually stopped.bin/devnow claims a worktree-scoped,
flock-backed session file (tmp/react_on_rails/dev-session.json) recording the app root it
belongs to, the process group it leads, and the Overmind endpoint it would use. Session
publication and kill-reader ownership are serialized through a fixed
tmp/react_on_rails/dev-session.lock: writers publish a same-directory temporary file with an
atomic rename while retaining the JSON file's lifetime lock, and a kill reader retains both locks
through shutdown cleanup so another reader cannot authenticate a stale or recycled process group.
A concurrentbin/devstart now exits non-zero with retry guidance instead of running unrecorded
while that shutdown lock is held.
bin/dev killshuts that session down through its own Overmind control socket or its own process group,
escalating fromTERMtoKILLonly for survivors, and reports success only after positively
observing that the owner, its process group, and its listeners are gone. Fallback port discovery
is restricted to LISTEN sockets whose process working directory is inside the current app root;
anything it cannot attribute is reported as a diagnostic and never signalled, and missing,
malformed, unreadable, or foreign session state fails closed rather than falling back to a broad
kill. The default-mode port list now covers the Shakapacker dev-server port
(SHAKAPACKER_DEV_SERVER_PORT, thendev_server.portinconfig/shakapacker.yml, then 3035)
instead of a hard-coded 3001, and a session records the ports it actually selected so a kill
verifies those rather than re-deriving a guess. Processes that deliberately leave the group with
setsid/daemonization remain out of scope, which is why Overmind (whose tmux server daemonizes) is
controlled through its socket. Breaking API changes for anyone calling
ReactOnRails::Dev::ServerManagerdirectly:.development_processesand.kill_running_processes
are removed,.kill_processesnow returns a status symbol (andbin/dev killexits non-zero when
it refuses or cannot verify a shutdown, withbin/dev cleanrefusing to delete bundles in that
case),.print_kill_summarytakes(status_symbol, blockers_array)instead of a boolean,
.kill_port_processesonly signals listening sockets whose process working directory is inside
the current app root, and.find_port_pidsis now restricted to listening sockets (it returns
those listeners other than pid ≤ 1 and the calling process, and does not filter by app root —
attribution happens in the kill path). Fixes
#4846.
#4937 and
#4964 by
justin808. -
[Pro] Surfaced missing RSC loadable stats in Node Renderer logs: The first missing
loadable-stats.jsonread now emits an actionableINFOdiagnostic per renderer process while
preserving fallback and retry behavior without replaying the server path to the browser. Fixes
#4731.
#4918 by
justin808. -
[Pro] OpenTelemetry propagation and existing-provider attachment now recover cleanly: Rails-to-renderer
requests replace stale mixed-case W3C propagation headers with each request attempt's current client-span context
without mutating caller-owned raw headers. Node Renderer attachment to an application-owned provider now rolls back
a newly installed tracing adapter when a conflicting sub-span integration rejects installation, so initialization can
be retried after the conflict is removed. Existing-provider mode also reports every ignored renderer-managed option
before provider and context checks, while both provider modes share one service-name precedence implementation.
#4956 by
justin808. -
[Pro] Raised the
jwtfloor to>= 2.8for reliable offline license validation on Ruby 3.4+:
jwt 2.8 first declaresbase64as a runtime dependency, so the gemspec now requiresjwt >= 2.8, < 4.
jwt 3.x remains supported and 4.x remains unsupported. Documentation now distinguishes the Pro Node renderer's
default Fastify 5 setup, which requires Node 20+ at startup, from itsengines.nodefloor of Node 18.19.0+
when applications use the documented Fastify 4-compatible dependency overrides. Fixes
#4730.
#4864 by
justin808.
Changed
- [Pro] License: React on Rails Pro moves to The React on Rails Pro License 3.0, an application of ShakaCode
Trust-Based Commercial Licensing that matches ShakaPerf: free in production for small organizations, charities,
educational institutions, and hospitals; 45-day production evaluation; 30-day grace after a subscription lapses;
the license key stays optional and only sets the attribution status.
Published gem metadata now usesLicenseRef-ReactOnRailsPro, and generator messages reflect these terms.
#5104 by
sashakhar1.