Expose React 19 root error callbacks (rootErrorHandlers) + hydration-mismatch debugging guide#3933
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds global rootErrorHandlers types and registry, builds per-root callback options passed into React roots, wires registration into setOptions/resetOptions, composes Pro internal and user recoverable-error handlers (with RSCRoute bailout suppression), adds unit/integration/Pro tests and dummy-app E2E, and updates docs and tooling config. ChangesRoot Error Callbacks Implementation
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 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 |
…ed isThenable Review fixes from the Claude Code review pass and Cursor Bugbot on PR #3933: 1. Dev-mode hydration logger no longer swallows React's default reporting or the component stack. The error is default-reported exactly once (reportError, falling back to console.error — mirroring React's own default) so window-'error' tooling keeps working, and the branded line now includes errorInfo.componentStack. 2. New buildRootErrorCallbackOptions option defaultReportingHandledInternally: Pro's RSC-wrapped hydrate paths pass it so the chained internal handler remains the single default reporter and the dev logger emits only its supplemental branded line (no second full error dump in development). Added a Pro test covering the chained dev path with railsEnv development. 3. getRootErrorHandlers now returns a snapshot copy so callers cannot mutate the internal registration and bypass validation. 4. Extracted the three byte-identical isThenable copies into packages/react-on-rails/src/isThenable.ts (exported as react-on-rails/@internal/isThenable) and removed the MUST-SYNC thenable-guard duplication note from the Pro renderer. 5. Partial rootErrorHandlers updates now merge per key (consistent with the other setOptions keys): setting only one callback keeps the others; an explicit undefined clears just that key; resetOptions still clears all. setOptions stores the merged result so option('rootErrorHandlers') reflects the effective registration. Docs (guide, JS API reference, type JSDoc) and the changelog entry updated to describe the preserved default reporting, component stack, and merge semantics. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
+ci-run-full Final readiness gate: local validation, codex review (3 passes, final clean), Claude Code review pass (4 findings fixed, 2 rejected with decision-log entries), Cursor Bugbot finding fixed, and /simplify (one reuse fix applied) are all complete for head a688679. Requesting full CI because this PR touches SSR/hydration behavior and the Pro/core boundary — path-selected CI is not sufficient evidence for this surface. |
Greptile SummaryThis PR adds a global
Confidence Score: 4/5The change is additive on the critical hydrate/render path and the core logic (snapshot semantics, Pro chaining, bailout filtering) is correct and thoroughly tested. The only issues found are a missing deduplication guard for the version-incompatibility warning and a stylistic inconsistency in base/client.ts; neither affects correctness or error recovery behavior. The new rootErrorHandlers module is well-designed with proper capture-at-creation semantics, safe async handler invocation, and correct Pro chaining. The dev logger avoids double reporting via defaultReportingHandledInternally. The version-incompatibility warning lacks the one-time guard claimed in the docs, meaning incremental setOptions calls on older React could emit repeated warnings, but this is purely a developer-experience issue. All behavioral paths have unit, integration, and e2e coverage. packages/react-on-rails/src/rootErrorHandlers.ts — the versionWarningEmitted deduplication is missing; packages/react-on-rails/src/base/client.ts — parameter-mutation pattern is inconsistent with capabilities/core.ts Important Files Changed
Sequence DiagramsequenceDiagram
participant App as App (setOptions)
participant RootEH as rootErrorHandlers module
participant Builder as buildRootErrorCallbackOptions
participant React as React (hydrateRoot/createRoot)
participant Pro as Pro chainRecoverableErrorHandlers
App->>RootEH: setOptions rootErrorHandlers
Note over RootEH: Merge per key, store in module-level registeredHandlers
App->>Builder: buildRootErrorCallbackOptions(context, shouldHydrate)
Note over Builder: Snapshot handlers from registeredHandlers
Builder-->>React: onRecoverableError wrapper + onCaughtError + onUncaughtError
React->>React: hydrateRoot or createRoot with options
rect rgb(200, 230, 255)
Note over React,Pro: OSS non-RSC path
React->>Builder: onRecoverableError fires
Builder->>Builder: defaultReportRecoverableError + logDevHydrationError
Builder->>App: safeInvoke userHandler with enriched context
end
rect rgb(230, 200, 255)
Note over React,Pro: Pro RSC hydrate path
React->>Pro: chainRecoverableErrorHandlers fires
Pro->>Pro: "filter RSCRoute ssr=false bailout"
Pro->>Pro: defaultReportRecoverableError internal
Pro->>Builder: call next with error and errorInfo
Builder->>Builder: logDevHydrationError only no double defaultReport
Builder->>App: safeInvoke userHandler with enriched context
end
Reviews (1): Last reviewed commit: "Reuse OSS defaultReportRecoverableError ..." | Re-trigger Greptile |
…ed isThenable Review fixes from the Claude Code review pass and Cursor Bugbot on PR #3933: 1. Dev-mode hydration logger no longer swallows React's default reporting or the component stack. The error is default-reported exactly once (reportError, falling back to console.error — mirroring React's own default) so window-'error' tooling keeps working, and the branded line now includes errorInfo.componentStack. 2. New buildRootErrorCallbackOptions option defaultReportingHandledInternally: Pro's RSC-wrapped hydrate paths pass it so the chained internal handler remains the single default reporter and the dev logger emits only its supplemental branded line (no second full error dump in development). Added a Pro test covering the chained dev path with railsEnv development. 3. getRootErrorHandlers now returns a snapshot copy so callers cannot mutate the internal registration and bypass validation. 4. Extracted the three byte-identical isThenable copies into packages/react-on-rails/src/isThenable.ts (exported as react-on-rails/@internal/isThenable) and removed the MUST-SYNC thenable-guard duplication note from the Pro renderer. 5. Partial rootErrorHandlers updates now merge per key (consistent with the other setOptions keys): setting only one callback keeps the others; an explicit undefined clears just that key; resetOptions still clears all. setOptions stores the merged result so option('rootErrorHandlers') reflects the effective registration. Docs (guide, JS API reference, type JSDoc) and the changelog entry updated to describe the preserved default reporting, component stack, and merge semantics. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
a688679 to
6d8c7cc
Compare
size-limit report 📦
|
Code ReviewOverviewThis PR adds a global Architecture assessmentCore module (
Pro chaining ( Issues found1. Minor:
|
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
docs/oss/building-features/debugging-hydration-mismatches.md (1)
1-219:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winEnsure the file ends with a newline character.
Per coding guidelines, all files must end with a newline. The annotation shows the file ending at line 219 without a visible newline marker.
🤖 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 `@docs/oss/building-features/debugging-hydration-mismatches.md` around lines 1 - 219, The file "Debugging Hydration Mismatches in Rails" is missing a trailing newline; open the document (look near the end after the "Related documentation" section / the last link to react.dev) and add a single newline character at EOF so the file ends with '\n' (ensure your editor saves with a final newline or run a formatting step that enforces it).Source: Coding guidelines
🧹 Nitpick comments (1)
docs/oss/building-features/debugging-hydration-mismatches.md (1)
58-59: 💤 Low valueTighten phrasing for conciseness.
Replace "at the moment it is created" with a shorter form like "when it is created" to reduce wordiness.
✏️ Suggested rephrase
- **Register before rendering.** Each root captures the callbacks registered at the moment it is created; register them in the same pack file where you call `ReactOnRails.register`. + **Register before rendering.** Each root captures the callbacks registered when it is created; register them in the same pack file where you call `ReactOnRails.register`.🤖 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 `@docs/oss/building-features/debugging-hydration-mismatches.md` around lines 58 - 59, Update the wording in the "Register before rendering." bullet to be more concise by replacing "at the moment it is created" with "when it is created"; this is in the sentence that references ReactOnRails.register so search for the phrase and change it to "when it is created" to tighten phrasing without altering meaning.Source: Linters/SAST tools
🤖 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 `@docs/oss/building-features/debugging-hydration-mismatches.md`:
- Around line 27-52: The example for ReactOnRails.setOptions rootErrorHandlers
uses context.componentName and context.domNodeId directly but RootErrorContext
defines them as optional; update the callbacks (onRecoverableError,
onCaughtError, onUncaughtError) to guard against undefined and provide fallbacks
(e.g., use null/unknown/default values or conditional checks) before passing
into myErrorReporter.report so you never access undefined properties from
context.componentName or context.domNodeId.
In `@packages/react-on-rails/src/base/client.ts`:
- Around line 183-190: The code only deletes newOptions.rootErrorHandlers when
it's !== undefined, causing setOptions({ rootErrorHandlers: undefined }) to be
treated as an unknown option; change the logic to mirror capabilities/core.ts by
checking property presence instead of typeof—i.e., use if ('rootErrorHandlers'
in newOptions) { if (typeof newOptions.rootErrorHandlers !== 'undefined') {
setRootErrorHandlers(newOptions.rootErrorHandlers);
this.options.rootErrorHandlers = getRootErrorHandlers(); } delete
newOptions.rootErrorHandlers; } so undefined is treated as a no-op and the key
is removed; reference symbols: newOptions.rootErrorHandlers,
setRootErrorHandlers, getRootErrorHandlers, this.options.rootErrorHandlers.
In `@packages/react-on-rails/src/rootErrorHandlers.ts`:
- Around line 49-63: The warnings in rootErrorHandlers fire on every call (e.g.,
from setRootErrorHandlers) because there's no guard; add a module-level boolean
flag (e.g., warnedUnsupportedRootErrorHandlers) and check it before calling
console.warn in both branches that reference supportsRootApi and
supportsReact19RootErrorCallbacks; set the flag to true after the first warn so
subsequent calls (even with different providedKeys/HANDLER_KEYS) are no-ops and
only the first warning is emitted. Ensure you update the branches that compute
providedKeys and react19OnlyKeys and reference HANDLER_KEYS, supportsRootApi,
and supportsReact19RootErrorCallbacks when applying the guard.
---
Outside diff comments:
In `@docs/oss/building-features/debugging-hydration-mismatches.md`:
- Around line 1-219: The file "Debugging Hydration Mismatches in Rails" is
missing a trailing newline; open the document (look near the end after the
"Related documentation" section / the last link to react.dev) and add a single
newline character at EOF so the file ends with '\n' (ensure your editor saves
with a final newline or run a formatting step that enforces it).
---
Nitpick comments:
In `@docs/oss/building-features/debugging-hydration-mismatches.md`:
- Around line 58-59: Update the wording in the "Register before rendering."
bullet to be more concise by replacing "at the moment it is created" with "when
it is created"; this is in the sentence that references ReactOnRails.register so
search for the phrase and change it to "when it is created" to tighten phrasing
without altering meaning.
🪄 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: c113314f-b0f1-4d57-8a8a-e07d5fd11a17
📒 Files selected for processing (27)
.lychee.tomlCHANGELOG.mddocs/oss/api-reference/javascript-api.mddocs/oss/building-features/debugging-hydration-mismatches.mddocs/sidebars.tspackages/react-on-rails-pro/src/ClientSideRenderer.tspackages/react-on-rails-pro/src/handleRecoverableError.client.tspackages/react-on-rails-pro/src/wrapServerComponentRenderer/client.tsxpackages/react-on-rails-pro/tests/ClientSideRenderer.test.tspackages/react-on-rails-pro/tests/wrapServerComponentRendererClient.errorCallbacks.test.tsxpackages/react-on-rails/package.jsonpackages/react-on-rails/src/ClientRenderer.tspackages/react-on-rails/src/base/client.tspackages/react-on-rails/src/capabilities/core.tspackages/react-on-rails/src/isThenable.tspackages/react-on-rails/src/reactApis.ctspackages/react-on-rails/src/rootErrorHandlers.tspackages/react-on-rails/src/types/index.tspackages/react-on-rails/tests/ReactOnRails.test.jsxpackages/react-on-rails/tests/rootErrorCallbacks.test.tsxpackages/react-on-rails/tests/rootErrorHandlers.test.tsreact_on_rails/spec/dummy/app/views/pages/root_error_callbacks.html.erbreact_on_rails/spec/dummy/client/app/packs/client-bundle.tsreact_on_rails/spec/dummy/client/app/startup/HydrationMismatchComponent.tsxreact_on_rails/spec/dummy/client/app/startup/RootErrorThrower.tsxreact_on_rails/spec/dummy/config/routes.rbreact_on_rails/spec/dummy/e2e/playwright/e2e/react_on_rails/root_error_callbacks.spec.js
Bundle-size audit — head
|
| Module | minified delta |
|---|---|
src/rootErrorHandlers.ts (new) |
+2,396 B (91%) |
src/capabilities/core.ts (setOptions/resetOptions wiring) |
+127 B |
src/isThenable.ts (new shared util, reused by Pro) |
+101 B |
src/reactApis.cts (React 19 callback support detection) |
+60 B |
src/ClientRenderer.ts (logic moved out) |
−69 B |
react-dom-client.production.js |
+20 B (minifier jitter; file byte-identical, cmp-verified) |
Verdict: all growth is intentional feature code. No new third-party modules, no duplicated modules, no barrel-import accidents; node_modules bit-identical.
One design note, surfaced for completeness: the dev hydration-mismatch reporting is gated at runtime via railsEnv (per the review-round commit), not process.env.NODE_ENV, so its message strings (~300–400 B of the gzip delta) ship in consumers' production bundles. That is a deliberate trade-off (dev reporting works regardless of how the consumer's bundler sets NODE_ENV); NODE_ENV-gating could claw back ~0.3 KB gzip at the cost of losing the dev messages in NODE_ENV=production dev setups.
…ed isThenable Review fixes from the Claude Code review pass and Cursor Bugbot on PR #3933: 1. Dev-mode hydration logger no longer swallows React's default reporting or the component stack. The error is default-reported exactly once (reportError, falling back to console.error — mirroring React's own default) so window-'error' tooling keeps working, and the branded line now includes errorInfo.componentStack. 2. New buildRootErrorCallbackOptions option defaultReportingHandledInternally: Pro's RSC-wrapped hydrate paths pass it so the chained internal handler remains the single default reporter and the dev logger emits only its supplemental branded line (no second full error dump in development). Added a Pro test covering the chained dev path with railsEnv development. 3. getRootErrorHandlers now returns a snapshot copy so callers cannot mutate the internal registration and bypass validation. 4. Extracted the three byte-identical isThenable copies into packages/react-on-rails/src/isThenable.ts (exported as react-on-rails/@internal/isThenable) and removed the MUST-SYNC thenable-guard duplication note from the Pro renderer. 5. Partial rootErrorHandlers updates now merge per key (consistent with the other setOptions keys): setting only one callback keeps the others; an explicit undefined clears just that key; resetOptions still clears all. setOptions stores the merged result so option('rootErrorHandlers') reflects the effective registration. Docs (guide, JS API reference, type JSDoc) and the changelog entry updated to describe the preserved default reporting, component stack, and merge semantics. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rebase replayed the changelog entry into the now-stamped 17.0.0.rc.3 section; this PR is unmerged so the entry belongs under Unreleased. The bundle-size skip file records the maintainer-approved waiver: the +0.75KB gzip client growth is the rootErrorHandlers feature itself (91% of the module-level delta) with no accidental imports, per the audit posted on PR #3933. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
6d8c7cc to
f1217ed
Compare
|
Rebased onto current main (
Only conflict this round was the |
Code Review: PR #3933 — Expose React 19 root error callbacks + hydration-mismatch guideOverviewThis PR wires React 18/19 root error callbacks ( Code Quality and DesignStrengths:
Findings1. In 2. The constant is exported from 3. This is safe for broad React version compatibility, but React 19 ships concrete types for each callback ( 4. Both 5. In 6. Missing test: explicit There is no test for 7. Dev-mode default attaches to every hydrating root, not just mismatching ones When SecurityNo concerns. User callbacks are wrapped in Performance
SummaryWell-designed, well-tested PR. Snapshot semantics, merge semantics, Pro chaining, and version gating are all correct. Test coverage is comprehensive (unit + integration + E2E + Pro). The main actionable items are: (1) document the |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
docs/oss/building-features/debugging-hydration-mismatches.md (2)
27-52: ⚡ Quick winConsider adding fallback values to match the internal default logger pattern.
The doc example passes
context.componentNameandcontext.domNodeIddirectly tomyErrorReporter.report(). Both properties are optional per theRootErrorContexttype, so they may beundefined. React on Rails' own default hydration logger (when no user callback is registered) applies fallbacks like'unknown'when these values are absent. The doc example should either show explicit fallbacks (context.componentName || 'unknown') or clarify that the reporter may receiveundefinedvalues.💡 Proposed example with fallback pattern
onRecoverableError: (error, errorInfo, context) => { myErrorReporter.report(error, { level: 'warning', - componentName: context.componentName, - domNodeId: context.domNodeId, + componentName: context.componentName || 'unknown', + domNodeId: context.domNodeId || 'unknown', }); },Apply the same pattern to the other callbacks and the Sentry example (lines 64–85) for consistency.
🤖 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 `@docs/oss/building-features/debugging-hydration-mismatches.md` around lines 27 - 52, The example in ReactOnRails.setOptions.rootErrorHandlers passes optional context fields directly (e.g., context.componentName, context.domNodeId) which may be undefined; update the onRecoverableError, onCaughtError, and onUncaughtError callbacks to supply explicit fallbacks when calling myErrorReporter.report (e.g., use componentName || 'unknown' and domNodeId || 'unknown' or similar defaults) and apply the same fallback pattern to the Sentry example so the reporter always receives concrete values rather than undefined.
58-58: 💤 Low valueMinor: Simplify phrasing for conciseness.
"At the moment it is created" can be shortened to "when it is created" for better readability.
✏️ Suggested edit
-Each root captures the callbacks registered at the moment it is created; register them in the same pack file where you call `ReactOnRails.register`. +Each root captures the callbacks registered when it is created; register them in the same pack file where you call `ReactOnRails.register`.🤖 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 `@docs/oss/building-features/debugging-hydration-mismatches.md` at line 58, Update the sentence to use more concise phrasing: replace "at the moment it is created" with "when it is created" in the line that discusses registration before rendering (the sentence referencing ReactOnRails.register and root callback capture) so it reads "Each root captures the callbacks registered when it is created; register them in the same pack file where you call ReactOnRails.register."
🤖 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.
Nitpick comments:
In `@docs/oss/building-features/debugging-hydration-mismatches.md`:
- Around line 27-52: The example in ReactOnRails.setOptions.rootErrorHandlers
passes optional context fields directly (e.g., context.componentName,
context.domNodeId) which may be undefined; update the onRecoverableError,
onCaughtError, and onUncaughtError callbacks to supply explicit fallbacks when
calling myErrorReporter.report (e.g., use componentName || 'unknown' and
domNodeId || 'unknown' or similar defaults) and apply the same fallback pattern
to the Sentry example so the reporter always receives concrete values rather
than undefined.
- Line 58: Update the sentence to use more concise phrasing: replace "at the
moment it is created" with "when it is created" in the line that discusses
registration before rendering (the sentence referencing ReactOnRails.register
and root callback capture) so it reads "Each root captures the callbacks
registered when it is created; register them in the same pack file where you
call ReactOnRails.register."
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 95d0d51a-55e1-4f06-9599-40167988a586
📒 Files selected for processing (28)
.bundle-size-skip-branch.lychee.tomlCHANGELOG.mddocs/oss/api-reference/javascript-api.mddocs/oss/building-features/debugging-hydration-mismatches.mddocs/sidebars.tspackages/react-on-rails-pro/src/ClientSideRenderer.tspackages/react-on-rails-pro/src/handleRecoverableError.client.tspackages/react-on-rails-pro/src/wrapServerComponentRenderer/client.tsxpackages/react-on-rails-pro/tests/ClientSideRenderer.test.tspackages/react-on-rails-pro/tests/wrapServerComponentRendererClient.errorCallbacks.test.tsxpackages/react-on-rails/package.jsonpackages/react-on-rails/src/ClientRenderer.tspackages/react-on-rails/src/base/client.tspackages/react-on-rails/src/capabilities/core.tspackages/react-on-rails/src/isThenable.tspackages/react-on-rails/src/reactApis.ctspackages/react-on-rails/src/rootErrorHandlers.tspackages/react-on-rails/src/types/index.tspackages/react-on-rails/tests/ReactOnRails.test.jsxpackages/react-on-rails/tests/rootErrorCallbacks.test.tsxpackages/react-on-rails/tests/rootErrorHandlers.test.tsreact_on_rails/spec/dummy/app/views/pages/root_error_callbacks.html.erbreact_on_rails/spec/dummy/client/app/packs/client-bundle.tsreact_on_rails/spec/dummy/client/app/startup/HydrationMismatchComponent.tsxreact_on_rails/spec/dummy/client/app/startup/RootErrorThrower.tsxreact_on_rails/spec/dummy/config/routes.rbreact_on_rails/spec/dummy/e2e/playwright/e2e/react_on_rails/root_error_callbacks.spec.js
✅ Files skipped from review due to trivial changes (2)
- .lychee.toml
- CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (20)
- docs/oss/api-reference/javascript-api.md
- packages/react-on-rails/package.json
- react_on_rails/spec/dummy/config/routes.rb
- packages/react-on-rails/tests/ReactOnRails.test.jsx
- react_on_rails/spec/dummy/client/app/packs/client-bundle.ts
- packages/react-on-rails-pro/src/handleRecoverableError.client.ts
- react_on_rails/spec/dummy/client/app/startup/HydrationMismatchComponent.tsx
- packages/react-on-rails/src/isThenable.ts
- docs/sidebars.ts
- packages/react-on-rails/src/capabilities/core.ts
- packages/react-on-rails/src/ClientRenderer.ts
- react_on_rails/spec/dummy/client/app/startup/RootErrorThrower.tsx
- packages/react-on-rails/src/reactApis.cts
- packages/react-on-rails-pro/src/ClientSideRenderer.ts
- packages/react-on-rails-pro/tests/wrapServerComponentRendererClient.errorCallbacks.test.tsx
- react_on_rails/spec/dummy/e2e/playwright/e2e/react_on_rails/root_error_callbacks.spec.js
- packages/react-on-rails/src/types/index.ts
- packages/react-on-rails/src/rootErrorHandlers.ts
- packages/react-on-rails/tests/rootErrorCallbacks.test.tsx
- packages/react-on-rails/src/base/client.ts
|
Pushed Also triaged the |
|
Review triage for current head Resolved these as non-blocking/stale review items without another code push.
No confirmed blocker, failing test, API break, security issue, or required validation gap identified. |
Code Review - PR 3933Overall: Well-architected feature. Snapshot semantics (handlers captured at root-creation time), safe-invoke wrapping both sync throws and async rejections, per-key merge with explicit-undefined-clears-one semantics, and the bailout filter in the Pro RSC chain are all correctly implemented. Test coverage is thorough, the JSDoc is unusually complete, and the decision log is genuinely useful for reviewers. Four observations follow: 1. Behavioral divergence for Pro RSC hydrate roots (medium - worth documenting in the guide)On Pro RSC hydrate paths, 2. MUST SYNC duplication in
|
|
Final Claude-review triage for current head Claude review completed successfully. Remaining unresolved threads are non-blocking nits or already-triaged maintainability/docs points; resolving without code churn.
No confirmed correctness regression, failing test, API break, security issue, or required validation gap remains from Claude review. |
Address-review summaryScan scope: live unresolved current-head Claude threads on Mattered
Optional
Skipped
Validation: docs/LLMS/sidebar/link/format checks and OSS RuboCop passed locally; pre-commit and pre-push hooks passed. Review threads are resolved with GraphQL unresolved count 0. Next default scan starts after this comment. Say |
|
Code Review — PR #3933 (rootErrorHandlers + hydration-mismatch guide) The architecture is solid and the test coverage is thorough. A few observations below. WHAT IS WELL-DONE
CONCERNS
The 12-line block that handles
The message in
When |
|
Claude-review closeout triage for current head
No confirmed blocker remains from Claude review. |
|
Gate readiness update for Batch 5 launch dependency on current head
Merge recommendation: ready for independent accelerated-RC finalization / maintainer merge review, but not agent-auto-mergeable until that finalizer requirement is satisfied. Batch 5 issue lanes remain blocked until this PR is merged into |
* origin/main: Expose React 19 root error callbacks (rootErrorHandlers) + hydration-mismatch debugging guide (#3933) Add post-merge audit scope resolver (#4029) Document continuous agent-run evaluation loop (#4028) Tools: add PR batch merge ledger (#3996) Add RSC FOUC acceptance coverage (#4033) Keep plan-pr-batch goal prompts under 4000 chars (#4025) docs(skills): file-collision check + goal-prompt size discipline for plan-pr-batch (#3914) Verify React 19.2 <Activity> with react_component (CSR + SSR-hydrate) + docs (#3938) # Conflicts: # llms-full.txt
* origin/main: Expose React 19 root error callbacks (rootErrorHandlers) + hydration-mismatch debugging guide (#3933) Add post-merge audit scope resolver (#4029) Document continuous agent-run evaluation loop (#4028) Tools: add PR batch merge ledger (#3996) Add RSC FOUC acceptance coverage (#4033) Keep plan-pr-batch goal prompts under 4000 chars (#4025) docs(skills): file-collision check + goal-prompt size discipline for plan-pr-batch (#3914) Verify React 19.2 <Activity> with react_component (CSR + SSR-hydrate) + docs (#3938) # Conflicts: # CHANGELOG.md # llms-full.txt
…ter-slice * origin/main: Expose React 19 root error callbacks (rootErrorHandlers) + hydration-mismatch debugging guide (#3933) Add post-merge audit scope resolver (#4029) Document continuous agent-run evaluation loop (#4028) Tools: add PR batch merge ledger (#3996) Add RSC FOUC acceptance coverage (#4033) Keep plan-pr-batch goal prompts under 4000 chars (#4025) docs(skills): file-collision check + goal-prompt size discipline for plan-pr-batch (#3914) Verify React 19.2 <Activity> with react_component (CSR + SSR-hydrate) + docs (#3938) # Conflicts: # llms-full.txt
…e-demo-cost-docs * origin/main: Expose React 19 root error callbacks (rootErrorHandlers) + hydration-mismatch debugging guide (#3933) Add post-merge audit scope resolver (#4029) Document continuous agent-run evaluation loop (#4028) Tools: add PR batch merge ledger (#3996) # Conflicts: # llms-full.txt
…ce-maps * origin/main: Expose React 19 root error callbacks (rootErrorHandlers) + hydration-mismatch debugging guide (#3933) Add post-merge audit scope resolver (#4029) Document continuous agent-run evaluation loop (#4028) Tools: add PR batch merge ledger (#3996) Add RSC FOUC acceptance coverage (#4033) Keep plan-pr-batch goal prompts under 4000 chars (#4025) docs(skills): file-collision check + goal-prompt size discipline for plan-pr-batch (#3914) Verify React 19.2 <Activity> with react_component (CSR + SSR-hydrate) + docs (#3938) # Conflicts: # llms-full.txt
…c-streaming * origin/main: Expose React 19 root error callbacks (rootErrorHandlers) + hydration-mismatch debugging guide (#3933) Add post-merge audit scope resolver (#4029) Document continuous agent-run evaluation loop (#4028) Tools: add PR batch merge ledger (#3996) Add RSC FOUC acceptance coverage (#4033) # Conflicts: # llms-full.txt
…style Rails bridge (#3872) (#3942) ## Summary Implements the maintainer-approved v1 of `useRailsForm` (approval comment 2026-06-11 on the issue; descoped per its "Triage — 2026-06-11" section). Closes #3872. An Inertia `useForm`-style bridge from React forms to plain Rails controller actions: - **New hook `useRailsForm`** (`react-on-rails/useRailsForm`): `data`/`setData`, per-field `errors`/`hasErrors`, `processing`, `wasSuccessful`, submit verbs (`post`/`put`/`patch`/`delete`/`submit`), `reset`/`clearErrors`/`setError`, automatic CSRF attachment from the Rails csrf-token meta tag (with a loud pre-fetch error if the tag is missing), JSON request/response handling, and mapping of `422` + `{ errors: { field: ["message"] } }` onto per-field error state. Non-2xx/non-422 responses reject with `RailsFormRequestError`. - **Opt-in gem concern** `ReactOnRails::Controller::FormResponders#render_model_errors(record)` renders ActiveModel errors in exactly that shape — per the triage AC correction, the documented contract is "a controller using the provided concern", not "unmodified controller". RBS signatures included. - **Redirect handling is minimal and forward-compatible with #3873**: success results expose `redirectTo` from safe same-origin JSON `redirect_to`/`redirectTo` hints, with a defensive custom-fetch redirected-response fallback; native Rails redirects reject because fetch uses `redirect: 'error'`. The hook never navigates. - **Dummy app example**: `/rails_form` (CSR component + `ContactMessagesController` + `ContactMessage` ActiveModel) exercising the full 422 round-trip. - **Docs**: new `docs/oss/building-features/forms.md` (quick start, Inertia `useForm` mapping table, error-shape contract, Server Functions #3867 cross-link, fetch-vs-XHR decision note). - **v1 is fetch-only**; `transform`, `recentlySuccessful`, and file-upload `progress` (the XHR fork) are deferred per triage. ## Codex Decision Log - **Non-blocking:** transport — **Decision:** fetch-only in v1 per the batch goal; the triage's fetch-vs-XHR criterion is documented (upload progress requires XHR/duplex, deferred). - **Non-blocking:** codex review P2 — same-tick `setData(...); submit(...)` posted stale data (React batches the state update). **Fixed:** `commitData` updates the data ref eagerly; regression test added. - **Non-blocking:** codex review P2 — a superseded submission could still fire `onSuccess` (e.g. navigate on `redirectTo`) after a newer one completed. **Fixed:** callbacks now use the same current-submission guard as state updates; regression test added. - **Non-blocking:** codex review P3 — docs quick start used `params.require(:contact_message)`, which raises for flat JSON bodies when JSON params wrapping is off. **Fixed:** docs match the dummy controller's flat-or-wrapped pattern. - **Non-blocking:** concern lives at `ReactOnRails::Controller::FormResponders` (room for future form helpers) with `render_model_errors` as the method name from the issue. ## Coordination Open PR #3933 touches the same npm package; overlap is limited to additive, line-disjoint changes in `CHANGELOG.md`, `docs/sidebars.ts`, `packages/react-on-rails/package.json` (one export line each), and `react_on_rails/spec/dummy/config/routes.rb` — trivially mergeable in either order. ## Test plan - Jest `tests/useRailsForm.test.tsx`: 40 tests — verbs, CSRF/headers, same-tick setData+submit, processing lifecycle, 422 mapping (incl. malformed/empty-shape rejection), non-browser URL-guard rejection, success/redirect, superseded-submission suppression, reset/clearErrors/setError, DOM render of per-field errors. Earlier full package suite: 199/199. - RSpec `spec/react_on_rails/controller/form_responders_spec.rb` (4 examples) and dummy `spec/requests/contact_messages_spec.rb` (6 examples: 422 shape for invalid submissions, 201 success, form page render): all pass. - `pnpm run build`, `type-check`, `lint`, Prettier, RuboCop (changed files), `rake rbs:validate`, `script/check-docs-sidebar`: all pass. - `codex review --base origin/main`: 3 findings (2×P2, 1×P3), all fixed with regression tests (see decision log). Labels: full-ci — new public API surface in both the npm package and the gem. 🤖 Generated with [Claude Code](https://claude.com/claude-code) ## Merge Readiness Criteria Current evaluation as of 2026-06-15 for head `ba57cc63f0280d3970c46e1dbf18a763be138b6b`. - **Release mode:** `accelerated-rc`, from canonical release gate #3823 (`Mode: accelerated-rc`). - **Current head SHA:** `ba57cc63f0280d3970c46e1dbf18a763be138b6b`. - **CI/check status:** Complete for current head. No required checks are reported for this branch, so the full check list is the gate: 50 passing, 5 expected skips (`docs-format-check`, benchmark/report/confirmation no-op jobs, and manual wrapper no-op paths), 0 failed, 0 pending. - **Review-thread status:** GraphQL sweep immediately before readiness refresh: unresolved review threads = 0. - **Review feedback triage:** Prior Claude/Codex/Cursor/CodeRabbit feedback has been fixed or explicitly triaged in the decision log and review-thread replies. The latest push only merged current `main` and resolved generated `llms-full` state after already-addressed useRailsForm feedback. - **Validation run:** `node script/generate-llms-full.mjs --check` -> passed; `git diff --check` -> passed; `pnpm start format.listDifferent` -> passed; `pnpm --dir packages/react-on-rails exec jest tests/useRailsForm.test.tsx --runInBand` -> passed, 49 tests; latest merge-update validation also passed the same focused Jest/LLMS/format/whitespace checks. - **Label/CI decision:** Labels: `full-ci`. New public npm hook, gem concern, RBS, dummy app, and docs justify full CI; full current-head GitHub gate is complete. - **Known residual risk:** Moderate new-public-API risk around form error and redirect edge cases. Existing local and CI coverage exercises the intended contract; no confirmed blocker remains. - **Merge recommendation:** Merge. Maintainer approval verified via #3942 (comment). Confidence note: - Validated: current-head full GitHub checks, 0 unresolved review threads, LLMS check, whitespace, Prettier, and focused `useRailsForm` Jest checks. - Evidence: GitHub checks for `ba57cc63f0280d3970c46e1dbf18a763be138b6b`; maintainer approval comment above; latest local validation commands recorded in this description. - UNKNOWN: none for the merge gate. - Residual risk: moderate public-API adoption risk, covered by focused tests and full CI. - Decision points: 1 (maintainer approval comment on 2026-06-15). <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > New public npm and gem APIs on form submission paths (CSRF, same-origin, error JSON); behavior is guarded and heavily tested but apps will rely on the 422 contract and verbatim ActiveModel messages. > > **Overview** > Adds an **Inertia `useForm`-style** bridge so React forms can post JSON to ordinary Rails controllers without a separate API layer. > > **Client:** New `useRailsForm` export at `react-on-rails/useRailsForm` (Pro re-exports the same path). The hook handles `data`/`setData`, `processing`, `wasSuccessful`, HTTP verbs, CSRF from the Rails meta tag, same-origin enforcement, `redirect: 'error'` on fetch, 422 → per-field `errors`, stale-submit suppression, and optional `redirectTo` from JSON hints (no auto-navigation). v1 is fetch-only. > > **Server:** Opt-in `ReactOnRails::Controller::FormResponders` with `render_model_errors(record)` returning `{ errors: { field: [messages] } }` at HTTP 422, plus RBS. > > **Shipped with:** `docs/oss/building-features/forms.md`, sidebar + `llms-full.txt`, CHANGELOG, dummy `/rails_form` + `ContactMessagesController`, and broad Jest/RSpec/request specs. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit ba57cc6. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> ## Agent Merge Confidence Mode: accelerated-rc Current head SHA: ba57cc6 Score: 8/10 Auto-merge recommendation: yes; maintainer-directed merge requested after conflict resolution and current-head review fixes Affected areas: useRailsForm hook/API, Rails form responder concern, dummy app form example, docs, generated llms reference CI detector: `script/ci-changes-detector origin/main` -> Ruby core, JS/TS, RSpec, dummy app, Pro JS/TS, uncategorized; recommends full lint/test/e2e/generator/Pro/node-renderer/benchmark coverage. Validation run: - `jest tests/useRailsForm.test.tsx --runInBand` -> passed locally on the final change set (49 tests). - `pnpm run type-check` in `packages/react-on-rails` -> passed locally. - `prettier --check` on touched hook/test/docs files -> passed locally. - `node script/generate-llms-full.mjs && node script/generate-llms-full.mjs --check` -> passed after docs/conflict updates. - `git diff --check` -> passed after latest conflict resolution. - Pre-push hooks on resolved heads -> RuboCop branch lint passed; Markdown link checks passed with lychee 0.23.0. Review/check gate: - GitHub checks: complete for `ba57cc63f0280d3970c46e1dbf18a763be138b6b`; 50 passed, 5 expected selector/placeholder skips, 0 failed. - Review threads: GraphQL unresolved count is 0. Two current-head Codex review findings were fixed and resolved: stale failure rejections and stale success results. - Current-head reviewer verdicts: - Claude review: passed for current head. - Cursor Bugbot: passed for current head. - CodeRabbit: passed for current head. Known residual risk: moderate-low; this is new API surface plus Rails integration, but full current-head CI, focused stale-submission regression coverage, docs drift checks, and resolved review findings reduce the known risk. Finalized by: maintainer-directed merge requested by @justin808 in Codex session on 2026-06-15; this is not an autonomous accelerated-RC merge. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Summary Stamps the **17.0.0.rc.4** changelog header and reconciles the post-rc.3 entries. ### What changed - **Stamped `### [17.0.0.rc.4] - 2026-06-14`** with the standard compare-link updates (`v17.0.0.rc.3...v17.0.0.rc.4`, `unreleased → rc.4...main`). Prior RC sections are left intact per the prerelease convention. - **Moved the source-mapped stack traces entry (PR 3940) into rc.4.** It had been placed inside the already-tagged `rc.3` section, but `#3940` merged *after* `v17.0.0.rc.3` was tagged (verified: `d9ac060a0` is not an ancestor of the rc.3 tag). - **Added the missing `[PR 4026]` attribution** to the RSC peer-compatibility (React 19.2 floor) entry, which previously linked only the issue. - **Merged the two duplicate `#### Added` headings** in the section into one. ### rc.4 contents - **Added**: cache_tags revalidation (#3964, Pro), React 19 root error callbacks (#3933), `useRailsForm` + `render_model_errors` (#3942), node-renderer `/health` & `/ready` endpoints (#3939, Pro), source-mapped stack traces (#3940, Pro) - **Changed**: RSC peer compatibility for the React 19.2 floor (#4026, Pro) - **Fixed**: Rspack generated apps start in HMR mode (#3926) Other merged PRs since rc.3 were docs / CI / tooling / test-infra and were intentionally not given entries (e.g. #3949 only touches `spec/support`; the user-facing generator hook is explicitly unaffected; #3953 adds zero runtime surface; #3934/#3938 are verify+docs). ### Notes - Verified clean under the repo's pinned `prettier@3.6.2`. The local pre-commit hook could not run prettier (binary not installed in this workspace), so the commit used `--no-verify`; CI prettier will run normally. After merge, run `rake release` (no args) to publish `v17.0.0.rc.4` and auto-create the GitHub release from this section. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Documentation-only changelog edits with no runtime or API surface changes. > > **Overview** > Adds the **`17.0.0.rc.4` (2026-06-14)** release section to `CHANGELOG.md` and updates the bottom compare links so **`[unreleased]`** points at `v17.0.0.rc.4...main` and **`[17.0.0.rc.4]`** covers `v17.0.0.rc.3...v17.0.0.rc.4`. > > The edit **re-sorts notes that landed after the rc.3 tag**: the Pro **source-mapped Node renderer stack traces** entry moves out of the rc.3 block into rc.4, and the duplicate **`#### Added`** under the RSC peer-compatibility **Changed** item is folded into a single Added list (health/ready probes stay documented under rc.4 Added). The React **19.2 floor** peer-compat line gains **[PR 4026]** attribution alongside the existing issue link. > > No application or library code changes—release documentation only. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 40c7a88. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Introduced `useRailsForm` hook for improved form handling * Added `/health` and `/ready` endpoints to Node renderer for better observability * Expanded React 19 support with root error callback registration * Enhanced Pro cache tag revalidation capabilities * **Changed** * Improved error diagnostics with source-mapped stack traces in Node renderer * Extended RSC peer compatibility for React 19.2.7+ <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Manual verification: reproduced the gap and confirmed the feature ✅Reproduced via the merged jest suites ( ReproductionSquash-merged at Results
The merged suites also cover the documented merge-per-key, snapshot-at-root-creation, single-key clear via CaveatMechanism-level in jsdom: forced hydration mismatches and the createRoot/error-boundary paths exercise the real |
Fixes #3892.
Summary
ReactHydrateOptionswas typed end-to-end but never constructed: the OSSClientRenderercalledreactHydrateOrRender(domNode, element, shouldHydrate)with no options, so apps could not observe recoverable hydration errors at all. This PR adds a public global registration API for React's root error callbacks and wires it into everyhydrateRoot/createRootcall React on Rails makes:(error, errorInfo)plus{ componentName, domNodeId }for the affected root.[ReactOnRails]line (component name, dom id, component stack, link to the new debugging guide) while React's default reporting stays intact — each error is default-reported exactly once (reportError, so dev overlays and window-'error'tooling keep firing): by Pro's internal handler on chained paths, by the core dev logger otherwise. In other environments nothing is attached unless registered.resetOptions).rootErrorHandlersupdates merge per key (registering onlyonCaughtErrorlater keeps an earlieronRecoverableError); an explicitundefinedclears a single key;resetOptionsclears all.console.warn(React <18 for everything; React 18 supportsonRecoverableErroronly).ClientSideRenderer,wrapServerComponentRenderer/client), the user callback is CHAINED after Pro's internalhandleRecoverableError— both always run; the internal handler is never clobbered. The RSCRoutessr:falsebailout signal (Pro control flow, not an app error) is filtered before both so it never reaches user error reporters. Pro'sidentifierPrefixclient mirror verified untouched.Time.current/timezones,current_user-conditional ERB, I18n locale drift, CSRF-token props, asset hosts, nondeterministic render) with fix patterns (props-not-ERB, client-only rendering, narrowly-scopedsuppressHydrationWarning), callback registration incl. a Sentry example, and the double-reporting precedence note (onCaughtErrorvs error boundaries). Added todocs/sidebars.ts.Descoped (phase 2)
onRecoverableError& co.) + hydration-mismatch debugging guide #3892's thread.HYDRATION_MISMATCH_GUIDE_URLinrootErrorHandlers.tswith aTODO(#3894).Test plan
packages/react-on-rails/tests/):rootErrorHandlers.test.ts— version gating (16/17/18/19) with warnings, dev-default attach rules, dev-log-before-user ordering, sync-throw and async-rejection isolation, snapshot-on-reset regression.rootErrorCallbacks.test.tsx— real react-dom 19: forced hydration mismatch invokes useronRecoverableErrorwith enriched context; branded dev message with guide link; createRoot path invokesonUncaughtError; error boundary path invokesonCaughtError.ReactOnRails.test.jsx— publicsetOptions/resetOptions/validation wiring.packages/react-on-rails-pro/tests/): chaining verified (user callback AND internal handler both fire; bailout suppressed from both; non-RSC paths get user callbacks without the internal handler); newwrapServerComponentRendererClient.errorCallbacks.test.tsxfor the RSC client wrapper; full Pro suite incl. RSC/streaming stays green (280 + 17 tests).react_on_rails/spec/dummy/e2e/playwright/e2e/react_on_rails/root_error_callbacks.spec.js): a server-rendered component with a forced mismatch firesonRecoverableErrorwith{componentName, domNodeId}; a client-rendered component throwing during render firesonUncaughtError. Passing on chromium, firefox, and webkit.Validation
pnpm run build/pnpm run lint/pnpm run type-check/pnpm start format.listDifferent— all pass(cd react_on_rails && BUNDLE_GEMFILE=../Gemfile bundle exec rubocop)— no offenses.claude/docs/playwright-e2e-testing.md)script/check-docs-sidebar origin/main— pass; lychee link check — 0 errorsreact_on_rails_pro/spec/dummy/ssr-generated/server-bundle.jsbuild artifact (absent in a fresh worktree); unrelated to this diff (no node-renderer code touched), 26/30 node-renderer suites pass.codex review --base origin/main): three passes; the first two findings (async-rejection isolation P3, handler-snapshot P2) accepted and fixed with regression tests; the final pass on the review-fix diff was clean./simplify(pinnedclaude-opus-4-8): one reuse finding accepted and applied ina6886795(Pro's localreportRecoverableErrorwas byte-identical to OSSdefaultReportRecoverableError; now a single exported implementation); all other findings verified false-positive or clarity-neutral and skipped. Targeted re-validation green (OSS 24/24, Pro 30/30, build/type-check/lint/format clean).d9169a68(dev logger forwardserrorInfo/component stack and preserves default reporting; no double console dump on the Pro chained dev path viadefaultReportingHandledInternally;getRootErrorHandlers()returns a copy;isThenableextracted to a shared@internalmodule), 2 rejected as speculative/structural (see decision log). Cursor Bugbot's partial-update merge-semantics finding fixed in the same commit.Codex Decision Log
Non-blocking: API shape for registering callbacks
setOptions({ rootErrorHandlers: {...} })per the issue suggestion; handlers stored inreact-on-rails/@internal/rootErrorHandlersmodule state (mirrors@internal/rendererTeardownprecedent) so OSS and Pro renderers share it without import cycles.Non-blocking: Pro chaining order and the RSCRoute
ssr:falsebailoutreportError→ user callback. The bailout digest is filtered from BOTH handlers.Non-blocking: dev-default scope
railsContext.railsEnv === 'development'; nothing is attached in other envs when no handler is registered.testenv should also get the branded log.Non-blocking: snapshot semantics
resetOptions.Non-blocking: deprecated
base/client.tsmirroredsetOptions({rootErrorHandlers})must not throw "Invalid options" on old Pro.Non-blocking:
.lychee.tomlexclusionspull/XXXXchangelog placeholder excluded until PR-number substitution.|XXXXalternation after substitution (coordinator handles).Non-blocking: single-report rule for dev-mode recoverable errors (review-fix batch)
defaultReportingHandledInternally: truetobuildRootErrorCallbackOptions), by the coredefaultReportRecoverableError(reportError→console.errorfallback, mirroring React's default) otherwise. The branded line never dumps the error object; it is purely supplemental context.componentStack/silenced window-'error'tooling, and double console dumps on the Pro chained dev path.onRecoverableErroris registered (dev-only; production follows React's own replace semantics). Maintainers could opt to suppress it when a user callback exists.Non-blocking: partial
rootErrorHandlersupdates merge per key (Cursor Bugbot finding)setRootErrorHandlersmerges; explicitundefinedclears one key;option('rootErrorHandlers')stores the effective merged snapshot.Non-blocking: two Claude-review findings rejected as code changes
ssr:falsebailout filter to non-RSC paths — unreachable today (RSCRoute requires an RSCProvider ancestor), convention-enforced invariant; (b) did not centralize option-building insidereactHydrateOrRender— structural refactor across six correct, unit-covered call sites.Labels: full-ci — touches SSR/hydration behavior and the Pro/core boundary (Pro recoverable-error chaining), which is explicitly listed as a full-CI trigger.
🤖 Generated with Claude Code
Note
Medium Risk
Changes global hydration and root error reporting across OSS and Pro RSC paths; incorrect chaining or double-reporting could affect dev tooling and production error monitors, though behavior is heavily tested.
Overview
Adds
ReactOnRails.setOptions({ rootErrorHandlers })so apps can register React 18/19 root error callbacks (onRecoverableError,onCaughtError,onUncaughtError) globally. A newrootErrorHandlersmodule validates and merges registrations per key, wraps callbacks with optionalcomponentName/domNodeId, and feedsbuildRootErrorCallbackOptionsinto every OSShydrateRoot/createRootpath (ClientRenderer,render, publicreactHydrateOrRender). In Rails development, hydrating roots also get a supplemental[ReactOnRails]hydration-mismatch log (with guide link and component stack) while preserving a single defaultreportErrorper error.React on Rails Pro chains user
onRecoverableErrorafter its internal recoverable handler on RSC hydrate paths (ClientSideRenderer,wrapServerComponentRenderer), filters the RSCRoutessr: falsebailout from both, and fixes cyclic cause chains in that filter. SharedisThenableand@internal/rootErrorHandlersexports support Pro without duplicating logic.Documentation adds Debugging Hydration Mismatches in Rails, updates the JavaScript API and changelog, dummy Playwright coverage, and CI tweaks (bundle-size skip note, lychee exclusions for the new doc URL and changelog PR placeholders).
Reviewed by Cursor Bugbot for commit 72decd2. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit
New Features
Documentation
Tests
Chores
Merge Readiness Criteria
Current evaluation as of 2026-06-15 for head
72decd2979a3b036f248c9a889d2b1d5fa9f2e15.accelerated-rc, confirmed live from canonical release gate Release gate: react_on_rails 17.0.0 #3823 (Mode: accelerated-rc).72decd2979a3b036f248c9a889d2b1d5fa9f2e15.gh pr checks 3933 --repo shakacode/react_on_rails --json bucketreports 49 passing checks and 7 expected path-selected/skipped checks; no pending or failing checks.mergeStateStatusisCLEAN.unresolved=0.[auto-deferred]rationale and resolved to avoid restarting the final-candidate gate.node script/generate-llms-full.mjs-> pass;node script/generate-llms-full.mjs --check-> pass;script/check-docs-sidebar-> pass;pnpm start format.listDifferent-> pass;bin/check-links docs/oss/api-reference/javascript-api.md-> pass;git diff --check --no-ext-diff-> pass;(cd react_on_rails && BUNDLE_GEMFILE=../Gemfile bundle exec rubocop)-> pass/no offenses; pre-commit and pre-push hooks -> pass.full-ci. This remains appropriate because the PR touches React 19 root error callback behavior and related docs; no benchmark label recommended.Agent Merge Confidence
Mode: accelerated-rc
Current head SHA: 72decd2
Score: 8/10
Auto-merge recommendation: no — the change appears merge-ready, but this block was prepared by @justin808/Codex and still needs an independent finalizer before accelerated-RC auto-merge.
Affected areas: React 19 root error callbacks, hydration/recoverable-error handling, Pro RSC client reporting, dummy app coverage, JavaScript API docs, error-reference docs.
CI detector:
script/ci-changes-detector origin/mainfrom a detached worktree at this head -> JavaScript/TypeScript code, dummy app, Pro JavaScript/TypeScript, uncategorized safety path; recommended lint, RSpec, JS unit tests, dummy integration, generator tests, Playwright E2E, Pro lint/RSpec/dummy/node-renderer, and benchmark workflows.Validation run:
gh/GraphQL review-thread sweep -> 94 total review threads, 94 resolved, 0 unresolved.claude-review, Cursor Bugbot, CodeRabbit, CodeQL, docs/sidebar/link/error-reference, llms-full, package, gem, dummy, generator, Pro, and integration checks are complete for this head.Review/check gate:
claude-reviewcheck passed.gh/GraphQL unresolved count is 0.Known residual risk: moderate surface area across React 18/19 callback behavior and Pro RSC reporting, but current-head CI and review coverage did not leave a confirmed blocker.
Finalized by: PENDING independent finalizer. This draft was added by @justin808 via Codex; @justin808 is also the PR author, so this does not satisfy the independent-finalizer rule.