Add declarative cache_tags revalidation to Pro fragment caching (revalidateTag analog)#3964
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:
WalkthroughImplements tag-based cache revalidation for React on Rails Pro: cached helpers register tags on cache writes, a TagIndex maps tags→expanded cache keys in Rails.cache, ChangesTag-Based Cache Revalidation
Sequence DiagramsequenceDiagram
participant View as Cached Helper
participant Cache as Cache module
participant TagIndex as TagIndex
participant Store as Rails.cache
View->>Cache: fetch_react_component(cache_tags: ['post:1'])
Cache->>Store: fetch(cache_key)
Store-->>Cache: nil (cache miss)
Cache->>View: yield block
View-->>Cache: rendered HTML
Cache->>Store: write(cache_key, html, expires_in: ...)
Cache->>TagIndex: register(['post:1'], cache_key, cache_options)
TagIndex->>TagIndex: normalize_tags(['post:1'])
TagIndex->>Store: read(rorp:tag:v1:post:1)
TagIndex->>Store: write(rorp:tag:v1:post:1, [expanded_keys...], ttl)
Note over View,Store: Later: revalidation triggered
View->>Cache: revalidate_tag('post:1')
Cache->>TagIndex: revalidate('post:1')
TagIndex->>Store: read(rorp:tag:v1:post:1)
TagIndex->>Store: delete_multi(expanded_keys)
TagIndex->>Store: delete(rorp:tag:v1:post:1)
TagIndex-->>Cache: 1 (deleted count)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 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 |
Greptile SummaryThis PR implements declarative
Confidence Score: 4/5Safe to merge. The two findings are quality/hardening concerns that do not affect correctness under standard Rails cache stores. The implementation is solid and the test suite is thorough — unit + integration across all four helper paths, FileStore round-trips, and fallback paths. Two concerns were found: normalized_entry_key uses private Rails cache store internals that will raise on non-standard stores, and revalidates_react_cache can silently double-register after_commit in subclass-override patterns. Neither affects behavior for standard Rails cache stores or single-class usage. cache/tag_index.rb (private-API reliance) and cache/revalidates.rb (duplicate callback guard) deserve a second look before the concern is widely adopted in subclass hierarchies. Important Files Changed
Sequence DiagramsequenceDiagram
participant Helper as cached_* helper
participant Cache as ReactOnRailsPro::Cache
participant TagIndex as Cache::TagIndex
participant RailsCache as Rails.cache
participant AR as ActiveRecord model (Revalidates)
Note over Helper,RailsCache: Cache MISS path
Helper->>RailsCache: fetch(cache_key) → miss
RailsCache-->>Helper: yield (render component)
Helper->>Cache: register_tags(cache_tags, cache_key, cache_options)
Cache->>TagIndex: register(tags, cache_key, opts)
TagIndex->>TagIndex: normalize_tags → flat String[]
TagIndex->>TagIndex: normalized_entry_key (via private send)
loop each tag
TagIndex->>RailsCache: read index_key(tag)
TagIndex->>RailsCache: "write index_key(tag), {keys:, expires_at:}"
end
Note over Helper,RailsCache: Cache HIT path
Helper->>RailsCache: fetch(cache_key) → hit
RailsCache-->>Helper: cached HTML (no tag registration)
Note over AR,RailsCache: Revalidation via AR concern
AR->>AR: after_commit fires
AR->>Cache: "revalidate_tags(*tags)"
Cache->>TagIndex: "revalidate(*tags)"
loop each tag
TagIndex->>RailsCache: read index_key(tag) → keys[]
TagIndex->>RailsCache: delete_multi(keys, namespace: nil)
TagIndex->>RailsCache: delete index_key(tag)
end
Reviews (1): Last reviewed commit: "Fix changelog PR link to actual PR numbe..." | Re-trigger Greptile |
Repeated calls (same class or subclass override) now replace the tag resolver without stacking duplicate after_commit callbacks. Addresses Greptile review finding on PR #3964. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
+ci-run-full |
|
Full-CI request reason (non-blocking decision, recorded per workflow): the |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9925453640
ℹ️ 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".
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 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 `@react_on_rails_pro/lib/react_on_rails_pro/cache.rb`:
- Around line 56-61: The revalidate_tags method currently delegates blindly to
TagIndex.revalidate and allows nil/blank tags to flow into
TagIndex.normalize_tags which raises; modify revalidate_tags to treat blank
inputs as a no-op by filtering the incoming *tags (e.g., reject
nil/empty/whitespace strings) and return 0 if the filtered list is empty,
otherwise call TagIndex.revalidate with the cleaned list; update the method
signature handling in revalidate_tags so callers like
ReactOnRailsPro.revalidate_tag(nil) no longer leak errors.
In `@react_on_rails_pro/lib/react_on_rails_pro/configuration.rb`:
- Line 118: Add validated setters for cache_tag_index_expires_in and
cache_tag_index_max_keys on the Configuration class: implement a
cache_tag_index_expires_in=(value) that raises unless value is a Numeric,
finite, and > 0, and implement cache_tag_index_max_keys=(value) that coerces to
Integer or raises unless it is an Integer > 0; ensure these setters are used
instead of allowing raw assignment (so consumers of Configuration get validation
before TagIndex.enforce_max_keys is called) and keep the existing reader names
intact so other code (e.g., TagIndex.enforce_max_keys) can rely on the validated
values.
In `@react_on_rails_pro/sig/react_on_rails_pro/cache.rbs`:
- Around line 43-45: The RBS for module ReactOnRailsPro::Cache::Revalidates only
declares def self.included; add a class-method signature for
revalidates_react_cache that matches the runtime API by declaring def
self.revalidates_react_cache which accepts an optional block parameter (the
resolver callback) and returns void; update the Revalidates module in the RBS to
expose this method so consumers receive types for the after_commit registration
helper.
In `@react_on_rails_pro/sig/react_on_rails_pro/configuration.rbs`:
- Around line 66-67: The RBS for Configuration is missing the two new optional
keyword parameters in the initialize signature; update def initialize for class
Configuration to include ?cache_tag_index_expires_in: Numeric? and
?cache_tag_index_max_keys: Integer? so the initialize type matches the actual
Ruby implementation (also keep the existing attr_accessor declarations
cache_tag_index_expires_in and cache_tag_index_max_keys unchanged); ensure the
initialize signature lists the two keywords with the same optional types and
nullability as the attr_accessors.
In `@react_on_rails_pro/spec/dummy/spec/models/cache_revalidates_spec.rb`:
- Line 24: Add a tightly scoped RuboCop inline disable on the RSpec.describe
line to suppress the file-path cops: append "# rubocop:disable
RSpec/FilePath,RSpec/SpecFilePathFormat" to the existing RSpec.describe
"ReactOnRailsPro::Cache::Revalidates", :caching line so the dummy spec path
warning is suppressed for this describe block.
🪄 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: 69a6067f-7a8c-4cd9-b846-f952ea113b21
📒 Files selected for processing (17)
CHANGELOG.mddocs/oss/building-features/caching.mddocs/pro/fragment-caching.mdllms-full.txtreact_on_rails_pro/app/helpers/react_on_rails_pro_helper.rbreact_on_rails_pro/lib/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro/cache.rbreact_on_rails_pro/lib/react_on_rails_pro/cache/revalidates.rbreact_on_rails_pro/lib/react_on_rails_pro/cache/tag_index.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/sig/react_on_rails_pro.rbsreact_on_rails_pro/sig/react_on_rails_pro/cache.rbsreact_on_rails_pro/sig/react_on_rails_pro/configuration.rbsreact_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rbreact_on_rails_pro/spec/dummy/spec/models/cache_revalidates_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/cache/tag_index_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/cache_spec.rb
Repeated calls (same class or subclass override) now replace the tag resolver without stacking duplicate after_commit callbacks. Addresses Greptile review finding on PR #3964. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
9925453 to
7d72ec8
Compare
|
+ci-run-full |
|
Full-CI re-request reason: mechanical rebase onto post-rc.3 main (llms-full.txt regeneration conflict only — zero source changes vs head 9925453, which passed the full 8-workflow suite). Re-running for current-head evidence before merge. |
Code Review — PR #3964: Tag-based cache revalidationOverviewThis is a well-designed, well-documented feature. The design contract (best-effort, lossy-OK index; correctness bounded by
Below are the issues I found, ordered by severity. Medium — Silent correctness failure when private Rails API is absent
The rationale is solid (public Suggestion: Add a Minor — Proc-to-nil produces a confusing error message
Minor —
|
There was a problem hiding this comment.
♻️ Duplicate comments (2)
react_on_rails_pro/spec/dummy/spec/models/cache_revalidates_spec.rb (1)
24-24:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winThe RuboCop suppression flagged in the previous review is still missing.
Line 24 uses a string description that does not mirror the dummy spec path, so
RSpec/FilePath/RSpec/SpecFilePathFormatwill flag this file. Based on learnings, specs underreact_on_rails_pro/spec/dummy/spec/should use# rubocop:disable RSpec/FilePath,RSpec/SpecFilePathFormatinline on thedescribeline.🤖 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 `@react_on_rails_pro/spec/dummy/spec/models/cache_revalidates_spec.rb` at line 24, Add the RuboCop suppression inline on the `describe` line: modify the RSpec declaration `RSpec.describe "ReactOnRailsPro::Cache::Revalidates", :caching do` to include `# rubocop:disable RSpec/FilePath,RSpec/SpecFilePathFormat` so the file won't be flagged by RSpec/FilePath or RSpec/SpecFilePathFormat.Sources: Coding guidelines, Learnings
react_on_rails_pro/lib/react_on_rails_pro/cache.rb (1)
56-62:⚠️ Potential issue | 🟠 Major | ⚡ Quick winThe blank-tag filtering issue flagged in the previous review is still present.
register_tags(line 51) short-circuits blank input, butrevalidate_tagsdelegates directly toTagIndex.revalidate, which callsnormalize_tagsand raises onnil/blank values (confirmed by tag_index_spec.rb lines 90-95). This asymmetry can surface errors fromReactOnRailsPro.revalidate_tag(nil)or resolver-driven paths that emit blank tags, breaking the no-op contract.🤖 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 `@react_on_rails_pro/lib/react_on_rails_pro/cache.rb` around lines 56 - 62, revalidate_tags currently delegates straight to TagIndex.revalidate which raises on nil/blank tags via normalize_tags, causing asymmetry with register_tags (which short-circuits blank input); update revalidate_tags to pre-filter/ignore nil or blank tag values (same normalization behavior register_tags uses) before calling TagIndex.revalidate so ReactOnRailsPro.revalidate_tag(nil) and resolver-driven blank tags become a no-op rather than raising; reference the revalidate_tags method and TagIndex.revalidate/normalize_tags to locate and adjust the behavior.
🤖 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.
Duplicate comments:
In `@react_on_rails_pro/lib/react_on_rails_pro/cache.rb`:
- Around line 56-62: revalidate_tags currently delegates straight to
TagIndex.revalidate which raises on nil/blank tags via normalize_tags, causing
asymmetry with register_tags (which short-circuits blank input); update
revalidate_tags to pre-filter/ignore nil or blank tag values (same normalization
behavior register_tags uses) before calling TagIndex.revalidate so
ReactOnRailsPro.revalidate_tag(nil) and resolver-driven blank tags become a
no-op rather than raising; reference the revalidate_tags method and
TagIndex.revalidate/normalize_tags to locate and adjust the behavior.
In `@react_on_rails_pro/spec/dummy/spec/models/cache_revalidates_spec.rb`:
- Line 24: Add the RuboCop suppression inline on the `describe` line: modify the
RSpec declaration `RSpec.describe "ReactOnRailsPro::Cache::Revalidates",
:caching do` to include `# rubocop:disable
RSpec/FilePath,RSpec/SpecFilePathFormat` so the file won't be flagged by
RSpec/FilePath or RSpec/SpecFilePathFormat.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 27ffe71f-d3ab-479e-9d90-d6388cf076ef
📒 Files selected for processing (17)
CHANGELOG.mddocs/oss/building-features/caching.mddocs/pro/fragment-caching.mdllms-full.txtreact_on_rails_pro/app/helpers/react_on_rails_pro_helper.rbreact_on_rails_pro/lib/react_on_rails_pro.rbreact_on_rails_pro/lib/react_on_rails_pro/cache.rbreact_on_rails_pro/lib/react_on_rails_pro/cache/revalidates.rbreact_on_rails_pro/lib/react_on_rails_pro/cache/tag_index.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/sig/react_on_rails_pro.rbsreact_on_rails_pro/sig/react_on_rails_pro/cache.rbsreact_on_rails_pro/sig/react_on_rails_pro/configuration.rbsreact_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rbreact_on_rails_pro/spec/dummy/spec/models/cache_revalidates_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/cache/tag_index_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/cache_spec.rb
💤 Files with no reviewable changes (1)
- llms-full.txt
✅ Files skipped from review due to trivial changes (3)
- docs/pro/fragment-caching.md
- CHANGELOG.md
- docs/oss/building-features/caching.md
🚧 Files skipped from review as they are similar to previous changes (9)
- react_on_rails_pro/sig/react_on_rails_pro.rbs
- react_on_rails_pro/sig/react_on_rails_pro/configuration.rbs
- react_on_rails_pro/sig/react_on_rails_pro/cache.rbs
- react_on_rails_pro/spec/react_on_rails_pro/cache_spec.rb
- react_on_rails_pro/lib/react_on_rails_pro.rb
- react_on_rails_pro/lib/react_on_rails_pro/cache/revalidates.rb
- react_on_rails_pro/lib/react_on_rails_pro/cache/tag_index.rb
- react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
- react_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb
- Require cache/tag_index from cache.rb so direct requires of the cache file keep working (Codex) - Blank tags in revalidate_tags are a no-op returning 0, mirroring register_tags (CodeRabbit) - Validate cache_tag_index_expires_in / cache_tag_index_max_keys with dedicated setters per the neighboring renderer-setting pattern (CodeRabbit) - Expose revalidates_react_cache and the private callback in the Revalidates RBS surface (CodeRabbit + Claude review) - Guard the private Store key-normalization API with a respond_to? check, one-time warning, and raw-key fallback for custom stores (Claude review) - Blank-tag error message now shows both the original entry and the resolved value so Proc-derived blanks are debuggable (Claude review) - Specs: legacy bare-array index payload, blank revalidate no-op, private-API fallback warning, config setter validation Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
+ci-run-full |
|
Full-CI re-request reason: review-fix commit 8640a9f addressing all 9 current-head review threads (blank-tag no-op, config setter validation, tag_index require, RBS surface, private-API guard + warning, error message, +4 specs). Local validation: 127 cache/config examples 0 failures, dummy concern 6/6, rubocop 13 files clean, RBS valid. Merging on green. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8640a9f716
ℹ️ 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".
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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 `@react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb`:
- Around line 1003-1008: Add a spec to ensure the setter for
cache_tag_index_expires_in rejects non-finite numbers: inside the existing
example that expects ReactOnRailsPro::Error from ReactOnRailsPro.configure {
|config| config.cache_tag_index_expires_in = ... }, add another expectation that
assigning Float::INFINITY to cache_tag_index_expires_in raises
ReactOnRailsPro::Error (using the same /cache_tag_index_expires_in/ matcher) so
the finite-number validation branch is covered.
- Around line 1010-1015: Update the spec that validates the
cache_tag_index_max_keys setter to include the zero boundary: in the example
using ReactOnRailsPro.configure { |config| config.cache_tag_index_max_keys = ...
} add an assertion that assigning 0 also raises ReactOnRailsPro::Error (matching
/cache_tag_index_max_keys/), so the test covers negative, zero, and non-integer
(e.g., 5.5) cases for cache_tag_index_max_keys.
🪄 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: 46bbd233-50a8-4e0d-ada8-02fac6d3556c
📒 Files selected for processing (6)
react_on_rails_pro/lib/react_on_rails_pro/cache.rbreact_on_rails_pro/lib/react_on_rails_pro/cache/tag_index.rbreact_on_rails_pro/lib/react_on_rails_pro/configuration.rbreact_on_rails_pro/sig/react_on_rails_pro/cache.rbsreact_on_rails_pro/spec/react_on_rails_pro/cache/tag_index_spec.rbreact_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
🚧 Files skipped from review as they are similar to previous changes (3)
- react_on_rails_pro/lib/react_on_rails_pro/cache.rb
- react_on_rails_pro/sig/react_on_rails_pro/cache.rbs
- react_on_rails_pro/spec/react_on_rails_pro/cache/tag_index_spec.rb
Code ReviewOverall AssessmentSolid, well-structured implementation with thorough documentation, a clear best-effort contract, and excellent test coverage (canary spec for private Rails API, FileStore round-trip, namespace isolation, delete ordering verification, destroyed-record normalization). The OSS package is untouched. No blocking issues; the notes below are a mix of medium-risk maintenance concerns and minor observations. Medium risk: private Rails cache store API
The risk is well-mitigated (canary spec, one-time warning + fallback on mismatch), but the canary covers only STI subclass identity caveat (undocumented)
This is expected AR behavior (each class has its own Minor observations
TestsCoverage is thorough: cache miss + HIT + revalidate round-trip, stream HIT→revalidate→MISS, async HIT→revalidate→MISS, destroyed records, Overall this is well-engineered for the best-effort contract it defines. The main ongoing cost is the private Rails API canary — worth keeping green on every Rails minor bump. |
|
Address-review pass for feedback after the previous summary cutoff ( Triage and outcome:
Validation evidence is in the PR body’s refreshed Merge Readiness Criteria section. Fresh GraphQL review-thread sweep reports |
|
Approved by maintainer. |
Pro Node Renderer Benchmark Summary
▲/▼ non-zero change vs baseline · 0.0% exact/near-zero match · 🔴 significant regression · 🟢 significant improvement (tracked measures) · (n) = baseline |
Core Benchmark Summary
▲/▼ non-zero change vs baseline · 0.0% exact/near-zero match · 🔴 significant regression · 🟢 significant improvement (tracked measures) · (n) = baseline |
Pro (shard 1/2) Benchmark Summary
▲/▼ non-zero change vs baseline · 0.0% exact/near-zero match · 🔴 significant regression · 🟢 significant improvement (tracked measures) · (n) = baseline |
Pro (shard 2/2) Benchmark Summary
▲/▼ non-zero change vs baseline · 0.0% exact/near-zero match · 🔴 significant regression · 🟢 significant improvement (tracked measures) · (n) = baseline |
* origin/main: Add declarative cache_tags revalidation to Pro fragment caching (revalidateTag analog) (#3964) Verify and document CSP-nonce propagation for streamed RSC under a strict no-unsafe-inline policy (#3934) Docs: document Control Plane cost posture for demos (#3998) # Conflicts: # docs/.llms-exclusions
…ter-slice * origin/main: Add useRailsForm hook + render_model_errors concern: Inertia useForm-style Rails bridge (#3872) (#3942) Add declarative cache_tags revalidation to Pro fragment caching (revalidateTag analog) (#3964) Verify and document CSP-nonce propagation for streamed RSC under a strict no-unsafe-inline policy (#3934) Docs: document Control Plane cost posture for demos (#3998) Add built-in /health and /ready endpoints to the Pro node renderer (#3939) Document capacity-aware triage contracts (#4027)
…ce-maps * origin/main: Add TanStack Router instant-navigation starter (Pro dummy) + docs guide (#3873 v1 slice) (#3953) Add useRailsForm hook + render_model_errors concern: Inertia useForm-style Rails bridge (#3872) (#3942) Add declarative cache_tags revalidation to Pro fragment caching (revalidateTag analog) (#3964) Verify and document CSP-nonce propagation for streamed RSC under a strict no-unsafe-inline policy (#3934) Docs: document Control Plane cost posture for demos (#3998) Add built-in /health and /ready endpoints to the Pro node renderer (#3939) Document capacity-aware triage contracts (#4027)
## 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: confirmed the feature behaves as documented ✅Reproduced via the merged Pro specs ( ReproductionSquash-merged at Results
CaveatConfirmed the tag-index read-modify-write, validation-at-registration, |
Summary
Implements the maintainer-signed-off RFC on #3871 (v1 scope; SWR explicitly deferred per the 2026-06-11 triage).
What:
cache_tags:on all four Procached_*helpers; aRails.cache-backed tag→key index;ReactOnRailsPro.revalidate_tag/revalidate_tags;ReactOnRailsPro::Cache::RevalidatesAR concern (revalidates_react_cache,after_commit); configcache_tag_index_expires_in(7d) /cache_tag_index_max_keys(5,000); docs (caching guide section with Next.js mapping, Pro fragment-caching section) and changelog.cache_key:semantics unchanged;cache_tags:is purely additive. Zero OSS package code changes.Contract (per RFC Option A): index appends are lossy-OK read-modify-write; tag revalidation is best-effort, correctness bounded by
expires_in(dev warning without an expiry); missing/evicted index → no-op returning 0. A Redis-native-sets fast path remains a welcome follow-up optimization, never a correctness requirement.Codex Decision Log
{keys:, expires_at:}(not bare array) to implement the RFC's max-TTL merge portably;v1key segment covers format evolution.expanded_key/namespace_key/merged_options(publicexpand_cache_keypreferscache_key_with_version+RAILS_CACHE_IDand would miss entries). FileStore encoding covered by spec.model_name.cache_key/id-- equal to version-lesscache_key, stable even withcache_versioning = false.expires_inwith aprevious_changesrecipe, not implemented -- per descoped-v1/no-over-engineering triage note./CHANGELOG.md(react_on_rails_pro/CHANGELOG.mdis a deprecation stub).llms-full.txtregeneration necessarily absorbs main's pre-existing staleness that open PR Regenerate stale llms-full.txt (fixes red check-llms-full on main) #3958 also fixes -- regenerate on rebase if Regenerate stale llms-full.txt (fixes red check-llms-full on main) #3958 lands first.revalidate_tag-> Decision: keep clearing the tag index before deleting entries. Reversing the order improves crash retryability for old entries, but it can delete fresh re-registrations discovered through a stale index snapshot. The retained order protects fresh content; crash-after-index-clear orphaning remains bounded by tagged entry expiry under the RFC's best-effort contract.rorp:tag:v1:so valid-but-long or control-character tag names do not become invalid Memcached-style cache keys.expires_athandling -> Decision: when Rails cache entries honorexpires_at, it takes precedence overexpires_in; older Rails strip unsupportedexpires_atand use the TTL the entry store will actually honor. Expired absolute times clamp to immediate expiry instead of propagating negative TTLs.Validation
bundle exec rspec spec/react_on_rails_pro/cache spec/react_on_rails_pro/cache_spec.rb→38 examples, 0 failuresbundle exec rspec spec/react_on_rails_pro→545 examples, 3 failures, 1 pending; the 3 failures (prod_binstub_spec.rb) pre-exist on clean origin/main (verified via stashed run:3 examples, 3 failures) — environment-related, untouched herebuild:testassets + node renderer on :3800):bundle exec rspec spec/helpers/react_on_rails_pro_helper_spec.rb spec/models/cache_revalidates_spec.rb→64 examples, 0 failures— includes the cached round-trip, stream HIT→revalidate→MISS, and async HIT→revalidate→MISS tag-busting proofsbundle exec rubocop --ignore-parent-exclusion(Pro) →235 files inspected, no offenses detected; OSS rubocop →218 files, no offensesbundle exec rake rbs:validate→✓ RBS validation passednode script/generate-llms-full.mjs --check→✓ llms-full.txt is currentscript/check-docs-sidebar→ pass;pnpm start format.listDifferent→ passReview gate
codex review --base origin/mainrun to completion 4 times; all findings fixed and re-reviewed (namespace-aware deletes,expires_atindex TTL, stable AR identity undercache_versioning = false, logical key recording + FileStore spec +delete_multifallback). A 5th confirmation pass timed out twice at the 580s tool cap with no findings emitted; recorded as incomplete rather than looping further.Closes #3871
🤖 Generated with Claude Code
Note
Medium Risk
Touches production fragment-cache read/write and invalidation across sync, stream, and async paths; behavior is best-effort and TTL-bounded, with broad test coverage but reliance on shared cache stores and custom stores honoring
namespace: nilon deletes.Overview
[Pro] Adds tag-based cache revalidation (Next.js
revalidateTag-style) on top of existing fragment caching: all fourcached_*helpers accept optionalcache_tags:, misses register keys in aRails.cachetag→key index, andReactOnRailsPro.revalidate_tag/revalidate_tagsdelete indexed entries.ReactOnRailsPro::Cache::Revalidates(revalidates_react_cache,after_commit) wires invalidation from ActiveRecord;cache_tag_index_expires_inandcache_tag_index_max_keysbound index growth. Helpers also normalizeexpires_atfor writes and skip caching when expiry has already passed.cache_key:behavior is unchanged; tagging is additive and documented as best-effort, with correctness bounded byexpires_in.Docs/changelog/llms outputs describe the contract and Next.js mapping;
docs/.llms-exclusionstrims some pages to stay under the llms cap;.lychee.tomlignoresvite.devin CI link checks.Reviewed by Cursor Bugbot for commit 14ead8d. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit
New Features
revalidate_tag/revalidate_tagsAPIs, plus model-level after-commit invalidation.cache_tagsare recorded only when a cache miss writes the entry.Documentation
Tests
Merge Readiness Criteria
Current evaluation as of 2026-06-15 for head
14ead8daca9e9a73044f66b800594447f9cb30bf.accelerated-rc, from canonical release gate Release gate: react_on_rails 17.0.0 #3823 (Mode: accelerated-rc).14ead8daca9e9a73044f66b800594447f9cb30bf.check-llms-full, Pro node renderer E2E/RSpec, Pro lint, package JS tests, examples, CodeQL,claude-review, Cursor Bugbot, CodeRabbit, and benchmark suites (Core benchmarks,Pro benchmarksshards 1/2 and 2/2,Pro Node Renderer benchmarks).mergeStateStatusisCLEAN.unresolved=0immediately before merge.llms-full.txtdrift against currentmain; no Bump react-hooks lint to v6 and document RSC compiler boundary #3963-reserved docs files were touched.git diff --check,node script/generate-llms-full.mjs --check,pnpm start format.listDifferent, OSS/Pro RuboCop, focused Pro cache specs, dummy cache revalidation spec, and helper spec after starting the node renderer all passing.full-ci,benchmark. This PR touches Pro fragment caching/cache tag revalidation and performance-sensitive paths, so both labels are appropriate; benchmark checks completed successfully.Agent Merge Confidence
Mode: accelerated-rc
Current head SHA:
14ead8daca9e9a73044f66b800594447f9cb30bfScore: 8/10
Auto-merge recommendation: yes, under explicit maintainer approval in the batch thread.
Affected areas: Pro fragment caching, cache tag revalidation, generated docs/LLMS, benchmarks.
CI detector: full CI + benchmark labels; current-head full CI and benchmarks completed.
Validation run:
Review/check gate:
14ead8daca9e9a73044f66b800594447f9cb30bf; skips are path-selected helper skips.claude-review, Cursor Bugbot, CodeRabbit completed with no unresolved blocker threads.Known residual risk: release-relevant cache invalidation change, mitigated by full CI, focused validation, and benchmark coverage.
Finalized by: maintainer approval in the Codex batch thread (
#3964marked “Approved by maintainer”).Confidence note:
gh pr checks 3964-> 33 pass / 4 skip / 0 pending / 0 failed; GraphQL review threads -> 85 total / 0 unresolved;mergeStateStatus->CLEAN.