Skip to content

Test React on Rails 17.0.0.rc.7#775

Merged
justin808 merged 3 commits into
masterfrom
chore/demo-fleet-17-rc7
Jul 8, 2026
Merged

Test React on Rails 17.0.0.rc.7#775
justin808 merged 3 commits into
masterfrom
chore/demo-fleet-17-rc7

Conversation

@justin808

@justin808 justin808 commented Jul 7, 2026

Copy link
Copy Markdown
Member

RC Test Report

Status: BLOCKED

This hard-gate demo-fleet PR tests the React on Rails 17.0.0.rc.7 package set in shakacode/react-webpack-rails-tutorial.

Target Versions

  • react_on_rails gem: 17.0.0.rc.7 (transitive through react_on_rails_pro)
  • react-on-rails npm package: 17.0.0-rc.7 (transitive through react-on-rails-pro)
  • react_on_rails_pro gem: 17.0.0.rc.7
  • react-on-rails-pro npm package: 17.0.0-rc.7
  • react-on-rails-pro-node-renderer npm package: 17.0.0-rc.7
  • react-on-rails-rsc npm package: 19.2.1-rc.0 (required by the rc.7 node renderer during RC soak)
  • shakapacker gem/npm package: unchanged at 10.2.0

Baseline

  • Last deployed baseline discovered from GitHub production release/deployment: production-2026-06-04-034633-26929107280, deployment 4928065047, commit c5f3b16f7e45dbf53c951eb6c81252e5102800d4, deployment status success.
  • Last deployed locks: react_on_rails/react_on_rails_pro 16.7.0.rc.3, Pro npm packages 16.7.0-rc.3, react-on-rails-rsc 19.0.4, shakapacker 10.1.0.
  • Current default branch baseline under test: origin/master at 196504a, with react_on_rails/react_on_rails_pro 17.0.0.rc.6, Pro npm packages 17.0.0-rc.6, react-on-rails-rsc 19.2.0, shakapacker 10.2.0.

Changes

  • Bumped the direct Pro Ruby/npm pins from rc.6 to rc.7 so the app resolves the underlying OSS react_on_rails gem and react-on-rails npm package at rc.7.
  • Bumped react-on-rails-rsc from 19.2.0 to 19.2.1-rc.0 because the rc.7 node renderer refuses to start with 19.2.0.
  • Updated Gemfile.lock, yarn.lock, and the README version-target block.
  • Did not bump shakapacker.

Feature Accounting

  • Ruby 3.3+ requirement: applicable and already satisfied by .ruby-version/Gemfile Ruby 3.4.6.
  • Removed config.server_render_method: not present.
  • Deprecated generated_assets_dirs, skip_display_none, defer_generated_component_packs: not present. Existing singular config.generated_assets_dir remains test-only app config.
  • hydrate_on scheduling/fixes: not used in this app.
  • RSC/SSR/Node Renderer: applicable. Build and RSC request specs pass, but one RSC client error-boundary system spec regresses under rc.7.
  • Typed response generation / createRailsAction / TanStack: not applicable; app has classic Rails/Jbuilder JSON and Redux, no TanStack or createRailsAction surface.
  • bin/dev clean and generator-created app files: not applicable to this dependency bump; this repo is not exercising install/generator output in this lane.
  • getStore(false): applicable through NavigationBarApp; comment route/system specs passed with the rc.7 renderer running.

Validation

  • bin/conductor-exec bundle install: PASS. Resolved react_on_rails and react_on_rails_pro to 17.0.0.rc.7; Bundler also updated transitive async/io-event.
  • bin/conductor-exec yarn install: PASS after correcting a local command typo (--frozen-lockfile=false is not valid Yarn classic syntax). Yarn warnings are existing peer warnings for React 19-era deps.
  • bin/conductor-exec yarn res:build && bin/conductor-exec bin/rails react_on_rails:locale && bin/conductor-exec bin/rails react_on_rails:generate_packs && bin/conductor-exec bin/shakapacker: PASS.
  • bin/conductor-exec bin/shakapacker: PASS after generated ReScript/locale/pack files existed. Cold direct run failed first because this app requires generated client/app/generated/server-bundle-generated.js; that matches the app initializer comments.
  • RENDERER_URL=http://localhost:3801 bin/conductor-exec bundle exec rspec: FAIL, 48 examples, 1 failure.
    • Failure: spec/system/server_components_demo_spec.rb:35
    • Expected the RSC ErrorBoundary fallback text Server component fetch failed after clicking Simulate Error.
    • Actual: browser remained on the previous LiveActivity content (Refresh count: 1), while the rc.7 node renderer logged Simulated server-side render failure (demo) for the LiveActivity RSC payload request.
  • Baseline comparison: current origin/master (17.0.0.rc.6 + react-on-rails-rsc 19.2.0) passes the same isolated example with its own renderer on port 3802:
    • RENDERER_URL=http://localhost:3802 bin/conductor-exec bundle exec rspec spec/system/server_components_demo_spec.rb:35: PASS, 1 example, 0 failures.
  • Smoke /:
    • Rails app started at http://127.0.0.1:3027 with RENDERER_URL=http://localhost:3801.
    • curl http://127.0.0.1:3027/: HTTP 200.
    • In-app browser loaded /; title React Webpack Rails Tutorial, H1 A cleaner way to explore Rails, React, and server rendering together., #RouterApp-react-component-0 present, #git-commit-sha present.
    • Console observed existing warnings plus React Intl missing locale data error; prior related tutorial issue: Fix i18n issue (Missing locale) #559.

Known Failures / Issues

Release Impact

Do not count this hard-gate lane as green for React on Rails 17.0.0 final until the rc.7 RSC error-boundary regression is fixed, explained, or explicitly waived.

Summary by CodeRabbit

  • Bug Fixes
    • Improved error normalization and recovery for live server activity failures, ensuring retries reliably bring the UI back from a fetch error.
    • Updated the error boundary fallback flow so the retry button re-renders the activity correctly after a failed refresh.
  • Documentation
    • Refreshed README version targets and demo/test copy to match the latest dependency versions and the updated “error recovery fallback” wording.
  • Chores
    • Bumped runtime and package versions to the latest release candidates.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5e10da3e-7ae6-46be-81c2-e7c225577403

📥 Commits

Reviewing files that changed from the base of the PR and between 3a8788b and 6da5a7d.

📒 Files selected for processing (1)
  • client/app/bundles/server-components/components/LiveActivityRefresher.jsx

Walkthrough

This PR reworks LiveActivityRefresher to normalize server-component refetch failures into ServerComponentFetchError, show a dedicated fallback UI, and retry with corrected props. It also updates related copy, tests, and dependency version targets.

Changes

Server component error recovery UI

Layer / File(s) Summary
Error normalization and retry state
client/app/bundles/server-components/components/LiveActivityRefresher.jsx
Imports ServerComponentFetchError, adds ActivityErrorFallback, toServerComponentFetchError, and ThrowActivityError, and rewires refresh/retry handling around fetchError, refreshCount, and routeRefreshKey.
Fallback rendering and route refresh
client/app/bundles/server-components/components/LiveActivityRefresher.jsx
Uses ActivityErrorFallback in ErrorBoundary, throws fetchError during render, and renders RSCRoute inside Suspense with corrected props and routeRefreshKey reset keys.
Copy and spec text
client/app/bundles/server-components/ror_components/ServerComponentsPage.jsx, spec/system/server_components_demo_spec.rb
Rewords the Live Server Activity description and the system spec description to match the new refetch and recovery fallback terminology.

Dependency version bumps

Layer / File(s) Summary
Dependency and target version updates
Gemfile, package.json, README.md
Bumps the React on Rails Pro-related dependency versions and updates the README version targets, including shakapacker.

Estimated code review effort: 2 (Simple) | ~15 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: testing React on Rails 17.0.0.rc.7 and related dependency bumps.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/demo-fleet-17-rc7

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

🚀 Quick Review App Commands

Welcome! Here are the commands you can use in this PR:
They require the repository to have cpflow review apps configured, including the CPLN_TOKEN_STAGING secret.

+review-app-deploy

Deploy your PR branch for testing.

+review-app-delete

Remove the review app when done.

+review-app-help

Show detailed instructions, environment setup, and configuration options.

Comment +review-app-help for full setup details.

@claude

claude Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review

This PR is a mechanical dependency bump (react_on_rails_pro/react_on_rails gem+npm, and react-on-rails-rsc, from rc.6 → rc.7), and the version references are internally consistent — Gemfile, Gemfile.lock, package.json, yarn.lock, and the README.md version-targets table all agree on 17.0.0.rc.7 / 19.2.1-rc.0, with no stale rc.6 references left behind.

However, the PR's own RC Test Report documents a confirmed regression and marks this lane BLOCKED — I'd treat that as a hard blocker, not just informational:

  • spec/system/server_components_demo_spec.rb:35 ("shows the ErrorBoundary fallback when Simulate Error is clicked, then recovers on Retry") fails under rc.7 but passes under the current master baseline (rc.6 + react-on-rails-rsc 19.2.0). The RSC ErrorBoundary no longer surfaces the Server component fetch failed fallback when the node renderer simulates a server-side render failure — the UI just keeps showing stale LiveActivity content instead.
  • This is a real, reproducible functional regression in error-handling behavior (not flaky/environmental), since the report explicitly re-ran the same isolated example against both renderer versions on different ports and got different results.

Given that, this shouldn't be merged as-is per the report's own conclusion — either the upstream rc.7 regression needs a fix/explanation, or this needs to be explicitly waived by whoever owns the react_on_rails 17.0.0 release gate. No other issues found in the diff itself.

@justin808

Copy link
Copy Markdown
Member Author

Fix pushed in 3a8788b.

What changed:

  • Moved the intentional LiveActivity failure demo onto the explicit RSC refetch recovery path: refetchComponent("LiveActivity", props, true) now captures the async ServerComponentFetchError, throws it through the existing ErrorBoundary, and Retry refetches corrected props.
  • Updated the Live Server Activity copy and the system spec description to describe error recovery rather than relying on prop-driven route failure propagation.

Local validation:

  • bin/conductor-exec yarn build:test — passed, existing webpack DefinePlugin warnings only.
  • bin/conductor-exec bundle exec rspec spec/system/server_components_demo_spec.rb:35 — passed, 1 example.
  • bin/conductor-exec bundle exec rspec spec/system/server_components_demo_spec.rb spec/requests/server_components_spec.rb — passed, 10 examples.
  • bin/conductor-exec yarn lint:eslint — passed with existing CommentsFeed.jsx react/no-danger warning.
  • bin/conductor-exec yarn prettier client/app/bundles/server-components/components/LiveActivityRefresher.jsx client/app/bundles/server-components/ror_components/ServerComponentsPage.jsx --check — passed.
  • git diff --check — passed.

Comment on lines 73 to 82
const handleSimulateError = () => {
setSimulateError(true);
setRefreshKey((k) => k + 1);
const nextKey = refreshKey + 1;
const errorProps = { simulateError: true, refreshKey: nextKey };

setFetchError(null);
setRefreshKey(nextKey);
refetchLiveActivity(errorProps).catch((error) => {
setFetchError(toServerComponentFetchError(error, errorProps));
});
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Redundant fetch on "Simulate Error"

handleSimulateError bumps refreshKey (line 78) and fires a manual refetchLiveActivity(errorProps) (line 79) with different props (simulateError: true).

Since refreshKey is also embedded in the componentProps used by the visible <RSCRoute> at line 105/143 ({ simulateError: false, refreshKey }), bumping it causes the mounted RSCRoute to independently re-fetch with simulateError: false — a legitimate, successful fetch that briefly renders (or holds Suspense) before being discarded the moment fetchError resolves and swaps the tree to <ThrowActivityError>.

So every "Simulate Error" click triggers two RSC round-trips: one real fetch that's thrown away, and one error-only fetch used to derive the failure. Net effect (besides the wasted request) is a possible flicker where real stats render for a moment before the error fallback replaces them.

Consider not bumping the display refreshKey for the visible RSCRoute when simulating an error (e.g. track "refresh count" in separate state from the value used as componentProps.refreshKey), so only the intentional error-fetch happens.

});
};

const buildBoundaryRetry = (resetErrorBoundary) => () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Minor: buildBoundaryRetry calls handleRetry() (which already bumps refreshKey/clears fetchError, causing the ErrorBoundary's resetKeys to change and auto-reset per react-error-boundary's own reset-on-resetKeys-change behavior) and then also explicitly calls resetErrorBoundary(). The explicit call looks redundant given resetKeys={[refreshKey]} below — harmless, but worth a comment explaining why both are needed if there's a reason (e.g. avoiding a one-render flash), otherwise consider dropping one.

Comment thread Gemfile

gem "cpflow", "5.1.1", require: false
gem "react_on_rails_pro", "17.0.0.rc.6"
gem "react_on_rails_pro", "17.0.0.rc.7"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Version bump to 17.0.0.rc.7 — per the PR description, this lane is reported as BLOCKED: one system spec (spec/system/server_components_demo_spec.rb:35) regresses under rc.7 vs. the rc.6 baseline. Given this, and that the RC report explicitly says "Do not count this hard-gate lane as green... until the rc.7 RSC error-boundary regression is fixed, explained, or explicitly waived," this PR shouldn't be merged as-is — worth confirming CI is green after the LiveActivityRefresher workaround before merging, and that the workaround doesn't just paper over the underlying regression for other (non-simulated) error paths.

@claude

claude Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Summary

Overview

This is a demo-fleet RC-validation PR bumping react_on_rails_pro/react-on-rails-pro/react-on-rails-pro-node-renderer to 17.0.0.rc.7 and react-on-rails-rsc to 19.2.1-rc.0, with Gemfile.lock/yarn.lock/README.md regenerated to match. It also reworks LiveActivityRefresher.jsx's error-simulation/retry flow (and the accompanying ServerComponentsPage.jsx copy + spec description) to adapt to what appears to be an RSC error-handling API change in rc.7 (errors returned as values from refetchComponent rather than thrown through Suspense).

Status flag

The PR's own description reports this lane as BLOCKED: a system spec regression (spec/system/server_components_demo_spec.rb:35, RSC ErrorBoundary fallback) fails on rc.7 but passes on the rc.6 baseline, and explicitly says not to count this as green until the regression is "fixed, explained, or explicitly waived." The diff includes what looks like an attempted fix for that regression in LiveActivityRefresher.jsx — worth confirming in CI whether it now passes, and whether the underlying regression (RSCRoute's own Suspense-thrown errors, used by the plain "Refresh" path) is still present outside the "Simulate Error" workaround path. Left an inline note on this.

Code quality — LiveActivityRefresher.jsx

  • Good: extracting ActivityErrorFallback, toServerComponentFetchError, and ThrowActivityError as top-level components/helpers cleans up the previous inline fallbackRender and resolves the react/no-unstable-nested-components concern for the card markup itself.
  • Possible inefficiency/UX flicker: handleSimulateError bumps the same refreshKey that's embedded in the visible RSCRoute's componentProps, which triggers a second, legitimate (simulateError: false) fetch that renders (or is in-flight) and then gets discarded as soon as the manually-fetched error resolves and the tree swaps to ThrowActivityError. See inline comment for a suggested fix (decouple the "Refresh count" display value from the fetch-triggering key).
  • Minor: buildBoundaryRetry calls both handleRetry() (which already changes resetKeys) and resetErrorBoundary() — likely redundant given react-error-boundary's own reset-on-resetKeys-change behavior. Not harmful, just worth double-checking/documenting.

Dependency bump

  • Gemfile/Gemfile.lock/package.json/yarn.lock/README.md changes are consistent and look mechanically correct (matching gem/npm versions, lockfile hash updates, transitive async/io-event bumps). No concerns there.

Test coverage

  • The spec changes are cosmetic (renamed it description only); assertions are unchanged. No new spec coverage was added for the double-fetch/flicker scenario noted above, but that's a minor gap for a demo app.

Security

  • No security concerns identified — this is a client-side error-handling/demo change plus routine dependency version bumps.

@justin808 justin808 marked this pull request as ready for review July 8, 2026 10:13
@greptile-apps

greptile-apps Bot commented Jul 8, 2026

Copy link
Copy Markdown

Greptile Summary

This PR tests the next React on Rails RC package set in the server-components demo. The main changes are:

  • Bumped the React on Rails Pro Ruby and npm packages to rc.7.
  • Bumped react-on-rails-rsc to 19.2.1-rc.0.
  • Refactored the LiveActivity error recovery flow around refetchComponent and ServerComponentFetchError.
  • Updated the demo copy, spec description, locks, and README version targets.

Confidence Score: 4/5

The changed RSC error recovery path can leave the demo showing stale content instead of the fallback.

  • The Simulate Error path no longer sends the simulated-error props through the rendered RSCRoute.
  • Fallback rendering now depends on the imperative refetch call rejecting in the expected shape.
  • Retry also resets the boundary before the corrected refetch has completed.

client/app/bundles/server-components/components/LiveActivityRefresher.jsx

Important Files Changed

Filename Overview
client/app/bundles/server-components/components/LiveActivityRefresher.jsx Refactors LiveActivity error handling to use fetchError state, ServerComponentFetchError, and imperative RSC refetches.
client/app/bundles/server-components/ror_components/ServerComponentsPage.jsx Updates the demo text to describe error recovery through the RSC refetch API.
spec/system/server_components_demo_spec.rb Renames the system spec example while keeping the same Simulate Error and Retry assertions.
Gemfile Pins react_on_rails_pro to 17.0.0.rc.7.
Gemfile.lock Updates the resolved Ruby dependency set for the rc.7 package target.
package.json Pins the React on Rails Pro npm packages to rc.7 and RSC to 19.2.1-rc.0.
yarn.lock Updates the resolved npm dependency set for the rc.7 and RSC RC package target.
README.md Updates the documented version targets.

Reviews (1): Last reviewed commit: "Fix RSC error recovery demo" | Re-trigger Greptile

Comment on lines +78 to +80
setRefreshKey(nextKey);
refetchLiveActivity(errorProps).catch((error) => {
setFetchError(toServerComponentFetchError(error, errorProps));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Error State Depends On Refetch

When Simulate Error is clicked, this path advances refreshKey but the rendered <RSCRoute> always receives simulateError: false; the fallback only appears if the imperative refetchComponent call rejects and sets fetchError. If the RSC refetch API returns the failed request without rejecting, the boundary never sees an error and the page keeps showing the stale LiveActivity content instead of Server component fetch failed.

Comment on lines +100 to +103
const buildBoundaryRetry = (resetErrorBoundary) => () => {
handleRetry();
resetErrorBoundary();
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Retry Resets Before Refetch

The retry handler starts refetchLiveActivity(correctedProps) and then resets the boundary immediately, before the async refetch can prime the cache described above. On retry, the boundary can remount <RSCRoute> before the corrected payload is cached, causing an avoidable duplicate fetch or skeleton flash instead of the intended cache-hit recovery path.

Comment on lines +93 to +97
// eslint-disable-next-line no-console
.catch((err) => console.error('Retry refetch failed:', err))
.finally(() => resetErrorBoundary());
.catch((err) => {
console.error('Retry refetch failed:', err);
setFetchError(toServerComponentFetchError(err, correctedProps));
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

eslint-disable-next-line only suppresses the rule on the line immediately following it. Here it precedes .catch((err) => { (line 94), not the actual console.error(...) call on line 95 — so the no-console violation is no longer suppressed where it matters. This worked in the old code because the callback was a single expression (.catch((err) => console.error(...))), but now that the callback has a multi-line body, the disable comment misses its target.

Suggested change
// eslint-disable-next-line no-console
.catch((err) => console.error('Retry refetch failed:', err))
.finally(() => resetErrorBoundary());
.catch((err) => {
console.error('Retry refetch failed:', err);
setFetchError(toServerComponentFetchError(err, correctedProps));
});
.catch((err) => {
// eslint-disable-next-line no-console
console.error('Retry refetch failed:', err);
setFetchError(toServerComponentFetchError(err, correctedProps));
});

Comment on lines +100 to +103
const buildBoundaryRetry = (resetErrorBoundary) => () => {
handleRetry();
resetErrorBoundary();
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This contradicts the comment above handleRetry (lines 84-85): "refetchComponent primes the cache with corrected props before resetting the boundary, so the post-reset render hits cache instead of re-fetching."

buildBoundaryRetry calls handleRetry() and then resetErrorBoundary() synchronously, back-to-back — it does not wait for the refetchLiveActivity(correctedProps) promise inside handleRetry to settle. The old code used .finally(() => resetErrorBoundary()) to guarantee the boundary only reset after the refetch completed (success or failure); that guarantee is gone now, so:

  • The boundary resets and RSCRoute re-renders/re-fetches before the priming request necessarily finished, which can defeat the cache-hit optimization the comment describes.
  • If the corrected-props refetch subsequently rejects, fetchError gets set again after the boundary already reset, producing a flicker (error → brief success/loading → error again).

Consider only resetting the boundary once the refetch promise resolves, e.g.:

const buildBoundaryRetry = (resetErrorBoundary) => () => {
  const newKey = refreshKey + 1;
  const correctedProps = { simulateError: false, refreshKey: newKey };
  setFetchError(null);
  setRefreshKey(newKey);
  refetchLiveActivity(correctedProps)
    .catch((err) => {
      console.error('Retry refetch failed:', err);
      setFetchError(toServerComponentFetchError(err, correctedProps));
    })
    .finally(() => resetErrorBoundary());
};

@claude

claude Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Summary

Overview

This is a hard-gate RC-soak PR bumping react_on_rails_pro/react-on-rails-pro (and transitively react_on_rails/react-on-rails) from 17.0.0.rc.617.0.0.rc.7, plus react-on-rails-rsc 19.2.019.2.1-rc.0 (required by the rc.7 node renderer). shakapacker is unchanged. Alongside the lockfile/README bumps, the PR modifies LiveActivityRefresher.jsx and its system spec to change how the RSC "Simulate Error" demo surfaces failures.

Most significant concern: the app-code change may mask the regression the PR itself flags as a release blocker

The PR description explicitly calls out a blocking rc.7 regression: "RSC client error recovery via RSCRoute + ErrorBoundary no longer shows the fallback for the simulated server-component error... rc.6 baseline passes; rc.7 fails."

However, LiveActivityRefresher.jsx was rewritten so that RSCRoute is now always rendered with componentProps.simulateError: false (see line 105/139) — it never actually requests the failing payload through the declarative RSCRoute + Suspense path anymore. Instead, handleSimulateError fires a separate, out-of-band refetchComponent('LiveActivity', { simulateError: true, ... }, true) call purely to detect the failure, then manually re-throws it via a synchronous-throw helper (ThrowActivityError) so ErrorBoundary catches it.

This means the demo/spec (spec/system/server_components_demo_spec.rb) no longer exercises the actual RSCRoute-driven Suspense error propagation that the regression is about — it now tests a hand-rolled catch-and-rethrow path instead. The section label and copy in ServerComponentsPage.jsx were also softened from "RSCRoute + ErrorBoundary" to "RSCRoute + Error Recovery," which reads as an acknowledgment that the original integration no longer works. Worth confirming with the team whether this workaround is meant to permanently ship (in which case the demo copy should be honest that this is app-level recovery, not built-in framework behavior), or whether it should be reverted so the spec keeps failing and visibly reflects the upstream regression until it's fixed.

Two concrete bugs found in the same file (see inline comments)

  1. Misplaced eslint-disable-next-line no-console — the disable comment precedes .catch((err) => {, not the console.error(...) line two lines below, so the lint suppression no longer covers the line it was meant for.
  2. resetErrorBoundary() called before the refetch promise settlesbuildBoundaryRetry invokes handleRetry() and resetErrorBoundary() back-to-back without awaiting the refetch inside handleRetry. This contradicts the adjacent comment ("primes the cache... before resetting the boundary") and can cause a redundant fetch or an error → success flicker → error-again UI sequence if the corrected-props refetch subsequently fails.

Dependency/version bump review

  • Gemfile/Gemfile.lock/package.json/yarn.lock/README version-targets block are all consistently bumped to rc.7 / 19.2.1-rc.0. No stray version mismatches spotted.
  • react_on_rails_pro's resolved dependency set changed (dropped explicit addressable/rainbow, gained nokogiri) — this looks like upstream gemspec churn rather than something introduced by this PR, but worth a sanity check that bundle install was run clean (the PR body says it was).
  • Pinning react-on-rails-rsc to an -rc.0 prerelease directly in package.json/Gemfile is expected for this hard-gate testing repo's purpose, but as always with RC pins, don't let this leak into a "stable" branch without re-pinning to a final release.

Test coverage

  • The system spec's it description was updated to match the new copy, but assertions are unchanged — as noted above, they no longer prove the original RSCRoute+ErrorBoundary integration works, only that the app-level fallback UI renders correctly.

Overall: the dependency bump itself looks mechanically sound, but the accompanying workaround in LiveActivityRefresher.jsx deserves scrutiny before merge — it may be quietly turning a red flag (real regression) back to green without actually fixing (or even continuing to demonstrate) the underlying issue.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3a8788b11e

ℹ️ About Codex in GitHub

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

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

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

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

Comment on lines +79 to +80
refetchLiveActivity(errorProps).catch((error) => {
setFetchError(toServerComponentFetchError(error, errorProps));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Ignore stale simulated-error refetches

With a slow RSC payload, if the user clicks Simulate Error and then recovers by clicking Refresh before this request rejects, this catch still stores the old { simulateError: true } failure in state. Because the current render always uses simulateError: false and there is no request id or refreshKey guard before setFetchError, a stale failure can replace the successfully refreshed card with the error fallback; ignore superseded refetches before updating fetchError.

Useful? React with 👍 / 👎.

@justin808

Copy link
Copy Markdown
Member Author

Follow-up pushed in 6da5a7d to address the review feedback on the first workaround pass.

Review feedback addressed:

  • Split refreshCount from the visible <RSCRoute> routeRefreshKey, so Simulate Error no longer triggers a redundant successful route fetch before the intentional error fallback.
  • Added a request-id guard so stale simulated-error/retry failures cannot overwrite a later refresh.
  • Retry now waits for the corrected refetchComponent request to succeed before updating the visible route key and resetting the boundary.
  • Moved the no-console suppression onto the actual console.error line.

Intentional scope note: this PR keeps the tutorial demo on the explicit RSC refetch recovery path. It does not claim to fix the upstream declarative RSCRoute/Suspense error propagation issue; that remains the separate rc.7 framework fix/verification path.

Fresh local validation on 6da5a7d:

  • bin/conductor-exec yarn build:test — passed, existing webpack DefinePlugin warnings only.
  • bin/conductor-exec bundle exec rspec spec/system/server_components_demo_spec.rb:35 — passed, 1 example.
  • bin/conductor-exec bundle exec rspec spec/system/server_components_demo_spec.rb spec/requests/server_components_spec.rb — passed, 10 examples.
  • bin/conductor-exec yarn lint:eslint — passed with the existing CommentsFeed.jsx react/no-danger warning.
  • bin/conductor-exec yarn prettier client/app/bundles/server-components/components/LiveActivityRefresher.jsx client/app/bundles/server-components/ror_components/ServerComponentsPage.jsx --check — passed.
  • git diff --check — passed.

Comment on lines 84 to 96
const handleSimulateError = () => {
setSimulateError(true);
setRefreshKey((k) => k + 1);
const requestId = nextRequestId();
const nextKey = refreshCount + 1;
const errorProps = { simulateError: true, refreshKey: nextKey };

setFetchError(null);
setRefreshCount(nextKey);
refetchLiveActivity(errorProps).catch((error) => {
if (latestRequestIdRef.current === requestId) {
setFetchError(toServerComponentFetchError(error, errorProps));
}
});
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

handleSimulateError only reacts to the rejected case of refetchLiveActivity. If the fetch were ever to resolve successfully here (e.g. the server stops honoring simulateError), nothing updates routeRefreshKey/re-renders RSCRoute, so the UI is left showing the previous card forever while "Refresh count" has already incremented — silently stale with no error and no new data. Since this path is only reached via the "Simulate Error" button (which is expected to always fail), this is low risk today, but it's worth at least a .then() branch (or a comment noting the assumption) so the code doesn't silently drift out of sync with refreshCount if the demo/server behavior changes.

end

it "shows the ErrorBoundary fallback when Simulate Error is clicked, then recovers on Retry" do
it "shows the error recovery fallback when Simulate Error is clicked, then recovers on Retry" do

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Per the PR description, this exact spec (server_components_demo_spec.rb:35) is reported as FAILING against the rc.7 node renderer (RENDERER_URL=http://localhost:3801 bundle exec rspec1 failure), while it passes against the rc.6 baseline. The PR's own "Release Impact" section says not to count this lane as green until the regression is fixed/waived.

Merging this PR as-is means master will carry a spec that's known to fail against the RC dependency set it just pinned to (rc.7 gem/npm pins in Gemfile/package.json). Suggest either:

  • Holding this PR until the upstream RSC error-boundary regression (react_on_rails#4357-adjacent) is resolved in rc.8+, or
  • If landing intentionally as a documented-red RC probe, marking this example pending/skipped with a comment linking the tracking issue, so CI doesn't silently show a real regression as routine noise.

@claude

claude Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review

Overview

This is the demo-fleet RC test lane bumping react_on_rails_pro (and transitively react_on_rails) from 17.0.0.rc.617.0.0.rc.7, plus react-on-rails-rsc 19.2.019.2.1-rc.0 (required for the rc.7 node renderer to start). Alongside the dependency bump, LiveActivityRefresher.jsx is substantially reworked to manually manage RSC fetch-error state (via a fetchError/ThrowActivityError render-phase throw) instead of relying on the previous flow where a failed refetchComponent naturally surfaced through RSCRoute's Suspense boundary into ErrorBoundary.

Correctness / Test Coverage

  • The PR's own report documents a known-blocking regression: spec/system/server_components_demo_spec.rb:35 ("shows the error recovery fallback...") fails against the rc.7 renderer even with this rewritten error-handling code, while the same spec passes against the rc.6 baseline. The PR description explicitly states this lane should not be counted green until the regression is fixed or waived. Left an inline comment flagging that merging this as-is puts a documented-failing spec on master alongside the version bump that causes it — recommend holding until resolved upstream, or explicitly marking the example pending with a tracking-issue link if landing intentionally as a red RC probe.
  • In LiveActivityRefresher.jsx, handleSimulateError only handles the rejected branch of refetchLiveActivity; there's no .then() for an unexpected success, which would leave the UI silently stale (refresh count incremented, no new card, no error). Low risk given "Simulate Error" is designed to always fail, but worth a comment or explicit branch — left inline.
  • The request-invalidation pattern (latestRequestIdRef) to ignore stale responses across concurrent Refresh/Simulate Error/Retry clicks is a solid, correct addition that wasn't present before.

Code Quality

  • The new toServerComponentFetchError / ThrowActivityError combo is a reasonable pattern for routing an imperatively-caught error into a declarative ErrorBoundary, but note it swaps the rendered element type (ThrowActivityError vs. Suspense>RSCRoute) each time fetchError toggles, which forces a full remount of that subtree rather than an update — acceptable here since there's no local state to preserve, but worth being intentional about if this pattern gets reused elsewhere.
  • Good factoring of the inline fallback JSX into ActivityErrorFallback and of the ad-hoc error-normalization into toServerComponentFetchError.
  • Comment updates (README wording, ServerComponentsPage.jsx copy, spec description) are consistent with the new manual-refetch-based error flow.

Security / Performance

  • No security concerns — error.message is only ever a demo/tutorial-internal error string, not user-supplied input.
  • No meaningful performance implications; this is a low-traffic demo page.

Dependency Bump

  • Gemfile/Gemfile.lock/package.json/yarn.lock changes are consistent and match the stated rc.6 → rc.7 (+ RSC 19.2.1-rc.0) targets. shakapacker intentionally left unchanged, matching the PR description.

Recommendation

Given this PR's own validation section reports a failing system spec caused by the very rc.7 bump it introduces, I'd treat this as not mergeable as green until the RSC error-boundary regression is resolved (or the failing spec is explicitly quarantined with a tracking link), per the PR's own "Release Impact" note.

@justin808 justin808 merged commit ed9c5d8 into master Jul 8, 2026
9 of 10 checks passed
@justin808 justin808 deleted the chore/demo-fleet-17-rc7 branch July 8, 2026 10:35
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

✅ Review App Deleted

Review app for PR #775 is deleted

🎮 Control Plane Console
📋 View Workflow Logs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@client/app/bundles/server-components/components/LiveActivityRefresher.jsx`:
- Around line 61-66: The refetchLiveActivity flow is passing an extra boolean to
refetchComponent and then checking for a resolved Error payload, which doesn’t
match the documented API or the existing rejection handling. Update
refetchLiveActivity in LiveActivityRefresher to call refetchComponent with only
the component name and props, and remove the payload instanceof Error branch so
failures are handled through the existing .catch path.
🪄 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: 5a26ca9c-6f84-4c02-9e54-fac995fa5205

📥 Commits

Reviewing files that changed from the base of the PR and between 196504a and 3a8788b.

⛔ Files ignored due to path filters (2)
  • Gemfile.lock is excluded by !**/*.lock
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (6)
  • Gemfile
  • README.md
  • client/app/bundles/server-components/components/LiveActivityRefresher.jsx
  • client/app/bundles/server-components/ror_components/ServerComponentsPage.jsx
  • package.json
  • spec/system/server_components_demo_spec.rb

Comment on lines +61 to +66
const refetchLiveActivity = (componentProps) =>
refetchComponent('LiveActivity', componentProps, true).then((payload) => {
if (payload instanceof Error) {
throw payload;
}
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does react-on-rails-pro's useRSC().refetchComponent accept a third boolean parameter, and can its returned promise resolve with an Error instead of rejecting?

💡 Result:

Based on the current React on Rails Pro documentation and implementation, here are the answers to your questions: 1. Does refetchComponent accept a third boolean parameter? No. The refetchComponent(name, props) method accepts only two parameters: the component name and the component props [1][2][3]. 2. Can its returned promise resolve with an Error instead of rejecting? The behavior of the returned promise depends on the nature of the failure: - Promise Rejection: The underlying RSC fetch typically results in a rejected promise when a network, renderer, or deployment failure occurs [4]. Recent updates have ensured that these failures consistently reject, and the internal cache is updated to evict failed promises so that subsequent retries can succeed rather than returning a "sticky" failed state [4][5]. - Error as a Value: Technical documentation notes that payloads which explicitly resolve with an Error value (rather than throwing/rejecting) are intentionally left in the cache [4]. This is distinct from a failed fetch that causes a rejection [4]. In summary, refetchComponent is designed for error-retry flows where you provide the component name and props; it does not take a third boolean parameter [1][6], and it expects standard promise rejection patterns for fetch failures [4][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'FILES:\n'
git ls-files | rg 'LiveActivityRefresher\.jsx|refetchComponent|useRSC|react_on_rails|server-components'

printf '\nUSAGES OF refetchComponent:\n'
rg -n "refetchComponent\(" client app . -g '!node_modules' -g '!dist' -g '!build' || true

printf '\nDEFINITIONS/EXPORTS AROUND useRSC:\n'
rg -n "useRSC|refetchComponent" client app . -g '!node_modules' -g '!dist' -g '!build' || true

Repository: shakacode/react-webpack-rails-tutorial

Length of output: 2370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'LIVEACTIVITYREFRESHER CONTEXT:\n'
cat -n client/app/bundles/server-components/components/LiveActivityRefresher.jsx | sed -n '1,140p'

printf '\nREADME CONTEXT:\n'
sed -n '210,250p' README.md

printf '\nREACT-ON-RAILS-PRO INITIALIZER:\n'
cat -n config/initializers/react_on_rails_pro.rb

Repository: shakacode/react-webpack-rails-tutorial

Length of output: 9589


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'PACKAGE/VERSION HINTS:\n'
rg -n "react-on-rails-pro|17\.0\.0-rc\.7|refetchComponent" package.json Gemfile Gemfile.lock yarn.lock pnpm-lock.yaml package-lock.json README.md config client -g '!node_modules' -g '!dist' -g '!build' || true

printf '\nRELEVANT DOCS SEARCH:\n'
rg -n "useRSC\(\)\.refetchComponent|refetchComponent\(name, props|refetchComponent" README.md docs client config -g '!node_modules' -g '!dist' -g '!build' || true

Repository: shakacode/react-webpack-rails-tutorial

Length of output: 5982


Drop the extra true and handle rejections directly. useRSC().refetchComponent(name, props) is documented as a two-argument call, and this path already has a .catch(...); the payload instanceof Error branch reads like it depends on an undocumented resolve-with-Error behavior.

🤖 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 `@client/app/bundles/server-components/components/LiveActivityRefresher.jsx`
around lines 61 - 66, The refetchLiveActivity flow is passing an extra boolean
to refetchComponent and then checking for a resolved Error payload, which
doesn’t match the documented API or the existing rejection handling. Update
refetchLiveActivity in LiveActivityRefresher to call refetchComponent with only
the component name and props, and remove the payload instanceof Error branch so
failures are handled through the existing .catch path.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant