Skip to content

Fix Pro FOUC reveal gating#4047

Merged
justin808 merged 8 commits into
mainfrom
jg-codex/fix/fouc-reveal-gating-4031-4032
Jun 16, 2026
Merged

Fix Pro FOUC reveal gating#4047
justin808 merged 8 commits into
mainfrom
jg-codex/fix/fouc-reveal-gating-4031-4032

Conversation

@justin808

@justin808 justin808 commented Jun 15, 2026

Copy link
Copy Markdown
Member

Fixes #4031.
Fixes #4032.

Summary

  • Gate Pro client-only auto-loaded component mounting on generated stylesheet readiness, including shared generated CSS discovered from manifest href metadata.
  • Promote streamed RSC client chunk stylesheet preloads to real stylesheet links before reveal, preserving preload attributes and handling split stream chunks.
  • Enable and extend FOUC Playwright coverage for CSS-before-reveal, JS-before-CSS, client-only gating, and CSS chunk scope.
  • Add the user-visible Pro fix to CHANGELOG.md.

Validation

  • pnpm exec jest tests/ClientSideRenderer.test.ts tests/injectRSCPayload.test.ts --runInBand
  • bundle exec rspec spec/helpers/react_on_rails_helper_spec.rb:63 spec/helpers/react_on_rails_helper_spec.rb:1402
  • pnpm run build
  • pnpm run type-check
  • pnpm exec eslint packages/react-on-rails-pro/src/ClientSideRenderer.ts packages/react-on-rails-pro/src/injectRSCPayload.ts packages/react-on-rails-pro/tests/ClientSideRenderer.test.ts packages/react-on-rails-pro/tests/injectRSCPayload.test.ts react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts
  • pnpm exec prettier --check packages/react-on-rails-pro/src/ClientSideRenderer.ts packages/react-on-rails-pro/src/injectRSCPayload.ts packages/react-on-rails-pro/tests/ClientSideRenderer.test.ts packages/react-on-rails-pro/tests/injectRSCPayload.test.ts react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts
  • bundle exec rubocop react_on_rails/lib/react_on_rails/helper.rb react_on_rails/lib/react_on_rails/pro_helper.rb react_on_rails/spec/dummy/spec/helpers/react_on_rails_helper_spec.rb
  • cd react_on_rails_pro/spec/dummy && pnpm run build:test
  • cd react_on_rails_pro/spec/dummy && pnpm exec playwright test e2e-tests/rsc_fouc.spec.ts
  • pnpm exec prettier --check CHANGELOG.md
  • git diff --check
  • /Users/justin/.agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main
  • Pre-push hook: branch RuboCop and online markdown links passed; lychee reported 14 redirects and 0 errors.

Review Notes

  • Codex autoreview: clean, no accepted/actionable findings.
  • Claude /code-review origin/main and /simplify origin/main were attempted with claude-opus-4-8 --max-budget-usd 20, but both CLI runs hung without output and returned only Execution error after interrupt.

Note

Medium Risk
Touches timing-sensitive Pro streaming and client hydration paths with substantial HTML stream rewriting; mitigated by scoped matching rules, inference timeouts, and fail-open behavior when CSS never loads.

Overview
Fixes Pro FOUC where client-only roots and streamed RSC Suspense reveals could show unstyled content because hydration/reveal ran before generated or chunk CSS was applied.

Client-only: Pro now waits in parallel for the component bundle and for matching generated stylesheet link elements (from data-generated-stylesheet-hrefs on the spec tag plus /generated/{Component} href heuristics), with a 10s fail-open timeout. Rails emits that metadata when auto_load_bundle resolves manifest preload hrefs for generated/{component}.

Streamed RSC: injectRSCPayload promotes only RSC client-chunk rel=preload as=style links to real stylesheets (data-precedence="rsc-css"), infers extra stylesheet tags from Flight + loadable-stats.json, and can defer $RC reveal HTML until inference finishes—while still streaming fallbacks and handling link tags split across chunks or UTF-8 boundaries. App-authored style preloads are left unchanged.

Playwright FOUC acceptance tests for #4031/#4032 are enabled (no longer fixme); Jest coverage expanded for both paths.

Reviewed by Cursor Bugbot for commit 69251bd. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed Flash of Unstyled Content (FOUC) in Pro with React Server Components by delaying client-only hydration until the component’s generated stylesheet links are ready (10s timeout) with “fails open” fallback.
    • Improved streamed RSC CSS handling by promoting streamed style preloads into real stylesheet links, handling links split across stream boundaries, and preserving app-authored preloads while ensuring correct reveal ordering.
  • Tests
    • Expanded Jest and stream-related coverage for stylesheet gating, reveal deferral, and UTF-8 boundary streaming edge cases.
    • Updated Playwright RSC FOUC acceptance tests to run normally and improved timing/cleanup.

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds end-to-end FOUC prevention for RSC and client-only components by coordinating server-side stylesheet href embedding, client-side CSS-ready gating before hydration, and RSC stream preload-to-stylesheet promotion including across chunk boundaries. Pending Playwright FOUC tests are now unconditionally enabled.

Changes

RSC/Client-Only FOUC CSS Gating

Layer / File(s) Summary
Ruby: embed generated stylesheet hrefs in component script
react_on_rails/lib/react_on_rails/pro_helper.rb, react_on_rails/spec/dummy/spec/helpers/react_on_rails_helper_spec.rb
generate_component_script emits data-generated-stylesheet-hrefs via a new generated_stylesheet_hrefs_json helper that returns a JSON array of unique stylesheet hrefs when auto_load_bundle is enabled; spec asserts the attribute is present.
Client: wait for generated stylesheets before hydration
packages/react-on-rails-pro/src/ClientSideRenderer.ts, packages/react-on-rails-pro/tests/ClientSideRenderer.test.ts
Reads data-generated-stylesheet-hrefs, matches against existing <link rel="stylesheet"> elements with URL normalization and /generated/${componentName} substring fallback, and awaits their load events (10s timeout, fails open) via Promise.all alongside component registry lookup before hydrating. Tests assert the wait and fail-open behaviors.
RSC stream: stylesheet inference infrastructure and gating
packages/react-on-rails-pro/src/injectRSCPayload.ts (lines 17–18, 95–337)
Introduces helpers to cache-load RSC client chunk stylesheet hrefs from loadable-stats.json, regex utilities to detect Suspense reveal boundaries and match preload link tags, functions to generate data-precedence="rsc-css" stylesheet links, and stateful gating that splits incomplete <link> tags across HTML buffer boundaries.
RSC stream: flush orchestration and per-stream tracking
packages/react-on-rails-pro/src/injectRSCPayload.ts (lines 369–377, 397–404, 419, 443–467, 496–511, 520–533, 642–660)
Extends injectRSCPayload signature with optional stylesheet href map parameter. Adds state for buffering inferred stylesheets, holding incomplete link tails, and tracking per-stream inference resolution with pending counters and timeout fallbacks. Updates flush() to apply preload promotion gating, defer flushing when incomplete tails are held, and reorder output to emit stylesheets before HTML. Disables incomplete-tail holding on stream termination. Tracks per-stream stylesheet inference with resolved flags and timeout fallbacks.
RSC stream: Flight parsing and inference resolution
packages/react-on-rails-pro/src/injectRSCPayload.ts (lines 680–687, 700–706)
Derives and buffers inferred stylesheets from parsed Flight data using chunk-name→href mapping. Wraps Flight parsing loop in try/finally to ensure inference resolution on both normal completion and error/early termination.
RSC stream stylesheet tests
packages/react-on-rails-pro/tests/injectRSCPayload.test.ts
Adds stream test utilities (createMockHTMLByteStream, createFlushingHTMLStream, collectStreamBuffer, collectStreamDataByChunk) and refactored setup (setupTestWithStreams). Comprehensive coverage: preload promotion to gated reveal stylesheets, inferred stylesheet injection ordering, deferred reveal HTML, Suspense fallback streaming, multi-stream inference gating, fail-open timeout, split-boundary reconstruction, app-authored preload preservation, and UTF-8 correctness.
E2E tests enabled and changelog
react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts, CHANGELOG.md
Removes REACT_ON_RAILS_RUN_PENDING_FOUC_TESTS flag and test.fixme guards from Playwright FOUC tests; updates the JS-finishes-first scenario to explicitly await the delayed webpack JS response. Adds afterEach cleanup hook. Changelog documents the full fix.

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant ProHelper as Ruby ProHelper
  participant injectRSCPayload
  participant ClientSideRenderer

  rect rgba(59, 130, 246, 0.5)
    note over ProHelper: Server render
    ProHelper->>Browser: component script with data-generated-stylesheet-hrefs=[...hrefs]
  end

  rect rgba(234, 179, 8, 0.5)
    note over injectRSCPayload: RSC stream processing
    injectRSCPayload->>injectRSCPayload: detect <link rel="preload" as="style"> in chunk
    injectRSCPayload->>injectRSCPayload: handle split tag across chunk boundary (holdIncompleteLinkTagTail)
    injectRSCPayload->>Browser: <link rel="stylesheet" data-precedence="rsc-css"> (before streamed HTML)
  end

  rect rgba(34, 197, 94, 0.5)
    note over ClientSideRenderer: Client hydration gating
    ClientSideRenderer->>Browser: parse data-generated-stylesheet-hrefs
    ClientSideRenderer->>Browser: match <link rel="stylesheet"> by href
    Browser-->>ClientSideRenderer: load event (or 10s timeout)
    ClientSideRenderer->>Browser: reactHydrateOrRender (component revealed)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • Follow-up: Fix client-only component reveal until CSS and JS are ready (#4031): This PR implements the CSS-ready gating for client-only components, directly addressing the failing test scenario where styled content should not appear until both CSS and JS have loaded.
  • Follow-up: Fix RSC streamed component CSS reveal gating to prevent FOUC (#4032): This PR implements the streamed RSC stylesheet inference and preload-to-stylesheet promotion, directly addressing all three failing RSC streaming test scenarios by ensuring CSS loads before content reveals.

Suggested labels

review-needed, P2

Suggested reviewers

  • AbanoubGhadban
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix Pro FOUC reveal gating' accurately summarizes the main change—implementing FOUC gating for client-only and streamed RSC components across the codebase.
Linked Issues check ✅ Passed All code changes fully implement the requirements from issues #4031 and #4032: client-only component gating in ClientSideRenderer.ts, streamed RSC stylesheet handling in injectRSCPayload.ts, and comprehensive test coverage across Jest and Playwright tests.
Out of Scope Changes check ✅ Passed All changes are directly scoped to addressing FOUC gating requirements: stylesheet loading coordination, RSC payload injection, test coverage, and helper methods for manifest-derived stylesheet hrefs—no unrelated modifications detected.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 jg-codex/fix/fouc-reveal-gating-4031-4032

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 and usage tips.

@justin808 justin808 marked this pull request as ready for review June 15, 2026 11:35
@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size
react-on-rails/client bundled (gzip) 62.35 KB (0%)
react-on-rails/client bundled (gzip) (time) 62.35 KB (0%)
react-on-rails/client bundled (brotli) 53.49 KB (0%)
react-on-rails/client bundled (brotli) (time) 53.49 KB (0%)
react-on-rails-pro/client bundled (gzip) 63.89 KB (+0.51% 🔺)
react-on-rails-pro/client bundled (gzip) (time) 63.89 KB (+0.51% 🔺)
react-on-rails-pro/client bundled (brotli) 54.87 KB (+0.54% 🔺)
react-on-rails-pro/client bundled (brotli) (time) 54.87 KB (+0.54% 🔺)
registerServerComponent/client bundled (gzip) 74.08 KB (+0.44% 🔺)
registerServerComponent/client bundled (gzip) (time) 74.08 KB (+0.44% 🔺)
registerServerComponent/client bundled (brotli) 63.76 KB (+0.31% 🔺)
registerServerComponent/client bundled (brotli) (time) 63.76 KB (+0.32% 🔺)
wrapServerComponentRenderer/client bundled (gzip) 66.76 KB (0%)
wrapServerComponentRenderer/client bundled (gzip) (time) 66.76 KB (0%)
wrapServerComponentRenderer/client bundled (brotli) 57.23 KB (0%)
wrapServerComponentRenderer/client bundled (brotli) (time) 57.23 KB (0%)

@greptile-apps

greptile-apps Bot commented Jun 15, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes two FOUC (Flash of Unstyled Content) bugs (#4031, #4032) in React on Rails Pro by gating component reveal on stylesheet readiness from two angles. Four previously-skipped Playwright FOUC tests are now enabled.

  • Client-only component gating (ClientSideRenderer.ts, pro_helper.rb): Before mounting a client-only root, the renderer now waits for all matching generated-stylesheet <link> elements to fire their load event. Matching uses manifest-derived hrefs serialized into the data-generated-stylesheet-hrefs attribute by Ruby, with a path-convention fallback and a 10-second fail-open timeout.
  • RSC streamed CSS promotion (injectRSCPayload.ts): Stylesheet <link rel="preload" as="style"> tags emitted for RSC client-chunk CSS are promoted to real <link rel="stylesheet"> tags (with data-precedence="rsc-css") before being flushed, including when the stream chunks split the tag mid-attribute or mid-token.
  • Tests: New unit tests for both the stylesheet-wait mechanism and the split-chunk promotion logic; four Playwright test.fixme blocks removed now that the underlying bugs are resolved.

Confidence Score: 4/5

Safe to merge; the new stylesheet-gating paths fail open on timeout or missing DOM links, so no regressions on existing pages without generated stylesheets.

Both FOUC gating mechanisms are carefully implemented with defensive fallbacks: the client-side renderer times out after 10 seconds rather than blocking forever, and the stream-side link-tag promotion correctly handles mid-chunk splits and restores the original buffer when nothing changes. The only non-trivial risks are the unescaped regex interpolation in getQuotedAttribute (safe with current callers) and the subtle hold of RSC init scripts behind an incomplete link-tag tail (intentional and correctly ordered). All four previously-skipped E2E tests are now active and the new unit tests cover the key split-chunk edge cases.

The split-chunk link-tag handling in injectRSCPayload.ts (the splitIncompleteLinkTagTail + early-return path in flush()) is the most intricate part of the change and worth a focused second read.

Important Files Changed

Filename Overview
packages/react-on-rails-pro/src/ClientSideRenderer.ts Adds FOUC gating for client-only component mounts: waits for matching generated-stylesheet links (via manifest hrefs or path convention) before mounting, with a 10s timeout fail-open. Logic is sound; silently skips gating if no matching link elements are in the DOM at mount time.
packages/react-on-rails-pro/src/injectRSCPayload.ts Promotes streamed RSC client-chunk stylesheet preloads to real stylesheet links before reveal, including cross-chunk split handling. The splitIncompleteLinkTagTail / early-return mechanism is correct but subtle; RSC init scripts are also held when an incomplete link tag stalls a flush (by design, to maintain init-before-HTML ordering).
react_on_rails/lib/react_on_rails/pro_helper.rb Adds data-generated-stylesheet-hrefs to the component spec tag using manifest-derived hrefs; guarded by auto_load_bundle, returns nil (omits attribute) when hrefs are empty.
packages/react-on-rails-pro/tests/ClientSideRenderer.test.ts Adds two new unit tests: manifest-derived shared stylesheet gating (waits for load event) and timeout fail-open (component renders after 10s even if stylesheet never fires).
packages/react-on-rails-pro/tests/injectRSCPayload.test.ts Adds four new unit tests for the stylesheet preload promotion: basic promotion, mid-href split, mid-token split, and app-authored preloads left untouched.
react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts Removes four test.fixme gates and improves the 'JS finishes first' test to wait for a delayed JS response instead of domcontentloaded, making the assertion more deterministic.
react_on_rails/spec/dummy/spec/helpers/react_on_rails_helper_spec.rb Adds auto_load_bundle: false to the existing stub and a new context verifying that manifest-derived hrefs are serialized into the component spec tag.

Comments Outside Diff (1)

  1. packages/react-on-rails-pro/src/injectRSCPayload.ts, line 278-295 (link)

    P2 RSC initialization scripts stall behind an incomplete link-tag tail

    When hasIncompleteLinkTagTail && holdIncompleteLinkTagTail triggers the early return, rscInitializationBuffers (and any accumulated rscPayloadBuffers) are also held — they are never cleared until a successful flush. This is intentional to preserve the "init before HTML" invariant, but it might be worth a brief inline comment explaining that the hold is deliberate so future readers don't interpret it as a missed flush path.

Reviews (1): Last reviewed commit: "Add changelog for Pro FOUC reveal gating" | Re-trigger Greptile

Comment thread packages/react-on-rails-pro/src/injectRSCPayload.ts
Comment thread packages/react-on-rails-pro/src/ClientSideRenderer.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/react-on-rails-pro/src/ClientSideRenderer.ts (1)

129-140: Use async/await in waitForGeneratedComponentStylesheets instead of .then(...) to align with repo conventions.

The function uses .then() at line 139, which conflicts with the TypeScript/JavaScript guideline that prefers async/await for promise handling.

Proposed refactor
-function waitForGeneratedComponentStylesheets(componentName: string, componentSpec: Element): Promise<void> {
+async function waitForGeneratedComponentStylesheets(
+  componentName: string,
+  componentSpec: Element,
+): Promise<void> {
   const generatedStylesheetHrefs = generatedStylesheetHrefsForComponent(componentSpec);
   const stylesheetLinks = Array.from(
     document.querySelectorAll<HTMLLinkElement>('link[rel~="stylesheet"][href]'),
   ).filter((link) => generatedStylesheetMatchesComponent(link, componentName, generatedStylesheetHrefs));
 
   if (stylesheetLinks.length === 0) {
-    return Promise.resolve();
+    return;
   }
 
-  return Promise.all(stylesheetLinks.map(waitForStylesheet)).then(() => undefined);
+  await Promise.all(stylesheetLinks.map(waitForStylesheet));
 }
🤖 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 `@packages/react-on-rails-pro/src/ClientSideRenderer.ts` around lines 129 -
140, Convert the waitForGeneratedComponentStylesheets function to use
async/await instead of .then() chaining to align with repository conventions.
Mark the function as async, replace the Promise.all().then(() => undefined)
chain with an await statement followed by the appropriate return, and ensure the
function still returns a Promise<void> as expected by its callers.

Source: Coding guidelines

🤖 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 `@packages/react-on-rails-pro/src/ClientSideRenderer.ts`:
- Around line 129-140: Convert the waitForGeneratedComponentStylesheets function
to use async/await instead of .then() chaining to align with repository
conventions. Mark the function as async, replace the Promise.all().then(() =>
undefined) chain with an await statement followed by the appropriate return, and
ensure the function still returns a Promise<void> as expected by its callers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: df5b240f-bb38-4f21-982f-564d0cb40497

📥 Commits

Reviewing files that changed from the base of the PR and between 39d93eb and a24b6dc.

📒 Files selected for processing (8)
  • CHANGELOG.md
  • packages/react-on-rails-pro/src/ClientSideRenderer.ts
  • packages/react-on-rails-pro/src/injectRSCPayload.ts
  • packages/react-on-rails-pro/tests/ClientSideRenderer.test.ts
  • packages/react-on-rails-pro/tests/injectRSCPayload.test.ts
  • react_on_rails/lib/react_on_rails/pro_helper.rb
  • react_on_rails/spec/dummy/spec/helpers/react_on_rails_helper_spec.rb
  • react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts

@justin808

Copy link
Copy Markdown
Member Author

Follow-up from the failed dummy-app-rspack-rsc-runtime-gate run:

Root cause: the Rspack RSC stream can reveal client-component HTML from the Flight $RC(...) script before the corresponding client CSS chunk is discoverable as a streamed <link rel="preload" as="style">. Webpack exercised the existing preload-promotion path, but Rspack only exposed the client chunk through the Flight reference, while loadable-stats.json had the CSS mapping.

Fix in 704ed821f: infer RSC client stylesheets from loadable-stats.json for clientN Flight references and flush those stylesheet links before streamed reveal HTML. I also added a regression test for that ordering and cleaned up Playwright routes after each FOUC test to prevent route callbacks from outliving the page/context.

Local validation:

  • pnpm exec jest tests/injectRSCPayload.test.ts --runInBand
  • pnpm run build
  • pnpm run type-check
  • cd react_on_rails_pro/spec/dummy && bundle exec rake react_on_rails:generate_packs && pnpm run build:test:rspack
  • cd react_on_rails_pro/spec/dummy && CI=true SHAKAPACKER_ASSETS_BUNDLER=rspack pnpm exec playwright test e2e-tests/rsc_echo_props.spec.ts e2e-tests/rsc_route_ssr_false.spec.ts e2e-tests/rsc_fouc.spec.ts --project=chromium
  • cd react_on_rails_pro/spec/dummy && SHAKAPACKER_ASSETS_BUNDLER=rspack pnpm e2e-test:rsc
  • pnpm run lint
  • pnpm start format.listDifferent
  • git diff --check

@cursor cursor 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.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is ON. A cloud agent has been kicked off to fix the reported issues.

Reviewed by Cursor Bugbot for commit 704ed82. Configure here.

Comment thread packages/react-on-rails-pro/src/injectRSCPayload.ts
Comment thread packages/react-on-rails-pro/src/ClientSideRenderer.ts

@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: 704ed821f0

ℹ️ 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 thread packages/react-on-rails-pro/src/injectRSCPayload.ts Outdated

@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: 915139ab5b

ℹ️ 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 thread packages/react-on-rails-pro/src/injectRSCPayload.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/react-on-rails-pro/src/injectRSCPayload.ts (1)

199-205: 💤 Low value

Static analysis flagged potential ReDoS, but risk is mitigated here.

The revealedContentId is extracted from React's own $RC() call and represents server-generated boundary IDs (e.g., "RscFoucProbe-react-component-0S:0"), not arbitrary user input. Combined with escapeRegExpLiteral sanitizing the value and the [^>]* patterns being bounded by the > delimiter, catastrophic backtracking is unlikely in practice.

Consider adding a length check on revealedContentId as a defensive measure if you want extra protection against malformed streams:

+  if (revealedContentId && revealedContentId.length < 256) {
-  if (revealedContentId) {
🤖 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 `@packages/react-on-rails-pro/src/injectRSCPayload.ts` around lines 199 - 205,
Add a defensive length check on the revealedContentId variable in the
hiddenBoundaryPattern block to protect against malformed streams that could
potentially cause ReDoS issues. Before constructing the RegExp object with the
hiddenBoundaryPattern regex, verify that revealedContentId is not excessively
long by adding a reasonable maximum length constraint (such as checking against
a defined constant or a reasonable upper bound) in addition to the existing
escapeRegExpLiteral sanitization.

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.

Nitpick comments:
In `@packages/react-on-rails-pro/src/injectRSCPayload.ts`:
- Around line 199-205: Add a defensive length check on the revealedContentId
variable in the hiddenBoundaryPattern block to protect against malformed streams
that could potentially cause ReDoS issues. Before constructing the RegExp object
with the hiddenBoundaryPattern regex, verify that revealedContentId is not
excessively long by adding a reasonable maximum length constraint (such as
checking against a defined constant or a reasonable upper bound) in addition to
the existing escapeRegExpLiteral sanitization.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5664e6b5-fd79-41d1-a60f-d330547594fb

📥 Commits

Reviewing files that changed from the base of the PR and between 915139a and 70bf6dc.

📒 Files selected for processing (2)
  • packages/react-on-rails-pro/src/injectRSCPayload.ts
  • packages/react-on-rails-pro/tests/injectRSCPayload.test.ts

@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: 70bf6dcf15

ℹ️ 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 thread packages/react-on-rails-pro/src/injectRSCPayload.ts

@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: 9ef364688f

ℹ️ 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 thread packages/react-on-rails-pro/src/injectRSCPayload.ts

@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: 1d45f32cfd

ℹ️ 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 thread packages/react-on-rails-pro/src/injectRSCPayload.ts Outdated
@justin808

Copy link
Copy Markdown
Member Author

Merge Ledger Block: formal review decision needed

Current head: 69251bdd4ae4efbf04e1457e128abf02eb994270

Closeout evidence:

  • GitHub checks are pass/skipped for the current head.
  • GraphQL unresolved current-head review threads: 0.
  • Strict merge ledger artifact: /tmp/pr-batch-4031-4032-4009/pr-4047-merge-ledger-resume.json.
  • Ledger status: complete_allowed: false only because pr.review_decision is UNKNOWN.

Maintainer action requested: leave a formal GitHub review decision (approve, request changes, or comment with the intended review state). After that, rerun script/pr-merge-ledger 4047 --repo shakacode/react_on_rails --changelog-classification changelog_present --finding-dispositions /tmp/pr-batch-4031-4032-4009/pr-4047-finding-dispositions.json --strict --pretty before any merge/readiness claim.

@justin808

Copy link
Copy Markdown
Member Author

Approved by maintainer, pending more adversarial AI reviews.

@github-actions

Copy link
Copy Markdown
Contributor

Pro Node Renderer Benchmark Summary

Benchmark RPS p50(ms) p90(ms) Status
Pro Node Renderer: simple_eval (non-RSC) 1167.11 ▼39.5% (1930.45) 4.62 ▲4.6% (4.42) 19.09 ▲115.0% (8.88) 200=35018
Pro Node Renderer: react_ssr (non-RSC) 1443.89 ▼17.1% (1740.71) 4.95 ▼2.4% (5.07) 12.29 ▲41.9% (8.66) 200=43318

▲/▼ non-zero change vs baseline · 0.0% exact/near-zero match · 🔴 significant regression · 🟢 significant improvement (tracked measures) · (n) = baseline

@github-actions

Copy link
Copy Markdown
Contributor

Core Benchmark Summary

Benchmark RPS p50(ms) p90(ms) Status
/: Core 3.46 ▼3.9% (3.6) 1805.11 ▼13.4% (2083.81) 2672.22 ▲1.0% (2646.37) 200=115
/client_side_hello_world: Core 668.78 ▼0.7% (673.72) 8.8 ▼1.8% (8.96) 15.55 ▼19.2% (19.25) 200=20210
/client_side_rescript_hello_world: Core 671.4 ▲0.6% (667.44) 9.31 ▲0.4% (9.27) 23.68 ▲22.1% (19.39) 200=20283
/client_side_hello_world_shared_store: Core 609.89 ▼3.0% (628.57) 9.5 ▼4.1% (9.9) 21.9 ▲9.9% (19.93) 200=18430
/client_side_hello_world_shared_store_controller: Core 626.63 ▼0.5% (629.9) 9.91 ▼0.1% (9.92) 21.31 ▲1.2% (21.05) 200=18933
/client_side_hello_world_shared_store_defer: Core 633.46 ▼2.1% (646.92) 9.95 ▲1.1% (9.84) 21.4 ▲8.3% (19.75) 200=19139
/server_side_hello_world_shared_store: Core 13.03 ▼15.8% (15.47) 453.71 ▼7.3% (489.24) 566.54 ▼12.9% (650.18) 200=401
/server_side_hello_world_shared_store_controller: Core 15.28 ▼1.1% (15.44) 529.74 ▲9.8% (482.62) 772.95 ▲17.6% (657.26) 200=471
/server_side_hello_world_shared_store_defer: Core 15.37 ▼1.3% (15.57) 414.88 ▼14.1% (483.12) 644.57 ▼2.8% (663.07) 200=473
/server_side_hello_world: Core 31.45 ▲0.6% (31.25) 287.95 ▲16.0% (248.14) 321.82 ▲6.1% (303.18) 200=953
/server_side_hello_world_hooks: Core 31.32 ▲1.3% (30.93) 265.24 ▲8.2% (245.24) 322.95 ▲5.2% (306.97) 200=952
/server_side_hello_world_props: Core 25.13 ▼18.4% (30.79) 188.75 ▼23.3% (246.02) 265.56 ▼13.9% (308.43) 200=771
/client_side_log_throw: Core 706.37 ▲5.0% (672.93) 8.76 ▼7.1% (9.43) 22.63 ▲20.5% (18.78) 200=21337
/server_side_log_throw: Core 30.09 ▼1.7% (30.6) 276.04 ▲7.9% (255.87) 334.73 ▲7.3% (311.94) 200=916
/server_side_log_throw_plain_js: Core 26.19 ▼15.4% (30.96) 226.33 ▼8.6% (247.65) 301.53 ▼2.4% (309.0) 200=797
/server_side_log_throw_raise: Core 30.68 ▼0.8% (30.93) 274.94 ▲11.1% (247.47) 321.2 ▲4.5% (307.38) 3xx=934
/server_side_log_throw_raise_invoker: Core 782.73 ▲0.2% (781.53) 10.12 ▲19.5% (8.47) 18.04 ▲10.8% (16.28) 200=23508
/server_side_hello_world_es5: Core 31.28 ▲1.9% (30.68) 267.73 ▲11.0% (241.17) 320.75 ▲5.6% (303.81) 200=950
/server_side_redux_app: Core 29.74 ▼1.1% (30.06) 282.05 ▲12.4% (250.97) 340.19 ▲9.5% (310.6) 200=903
/server_side_hello_world_with_options: Core 25.25 ▼19.5% (31.35) 180.53 ▼27.1% (247.77) 269.25 ▼12.2% (306.65) 200=773
/server_side_redux_app_cached: Core 656.39 ▼1.2% (664.19) 10.29 ▲2.9% (10.0) 18.8 ▲5.9% (17.75) 200=19827
/client_side_manual_render: Core 649.46 ▼3.1% (670.25) 8.67 ▼8.5% (9.47) 24.09 ▲25.1% (19.26) 200=19622
/render_js: Core 33.29 ▼2.3% (34.07) 249.96 ▲5.4% (237.17) 297.73 ▲4.8% (284.07) 200=1015
/react_router: Core 22.05 ▼24.6% (29.25) 270.93 ▲2.8% (263.61) 361.06 ▲10.2% (327.65) 200=671
/pure_component: Core 31.37 ▼2.5% (32.16) 289.12 ▲14.0% (253.61) 320.21 ▲5.9% (302.43) 200=952
/react_compiler_example: Core 30.98 ▼1.3% (31.4) 271.89 ▲11.7% (243.41) 329.11 ▲7.7% (305.69) 200=940
/css_modules_images_fonts_example: Core 30.79 ▼0.3% (30.89) 267.53 ▲7.0% (249.91) 318.64 ▲4.1% (306.12) 200=939
/turbolinks_cache_disabled: Core 650.39 ▼4.4% (680.51) 11.3 ▲21.1% (9.33) 22.64 ▲17.8% (19.22) 200=19649
/rendered_html: Core 31.61 ▲0.2% (31.54) 265.17 ▲6.7% (248.58) 310.57 ▲2.2% (303.83) 200=962
/xhr_refresh: Core 15.98 ▲0.8% (15.85) 531.61 ▲13.0% (470.36) 705.21 ▲11.6% (631.68) 200=489
/react_helmet: Core 30.36 ▼1.5% (30.82) 268.28 ▲8.0% (248.33) 322.95 ▲4.2% (309.87) 200=927
/broken_app: Core 29.8 ▼2.9% (30.68) 279.16 ▲10.1% (253.66) 328.63 ▲4.5% (314.49) 200=907
/image_example: Core 30.52 ▼1.7% (31.04) 199.39 ▼21.6% (254.48) 310.27 ▲0.3% (309.46) 200=933
/font_optimization_example: Core 536.7 ▼29.8% (764.86) 7.47 ▼9.6% (8.26) 11.88 ▼29.2% (16.78) 200=16332
/client_side_activity: Core 712.47 ▲15.5% (616.72) 8.56 ▼16.1% (10.2) 22.75 ▲11.6% (20.39) 200=21522
/server_side_activity: Core 31.05 ▲6.2% (29.22) 272.31 ▲3.0% (264.46) 327.16 0.0% (327.26) 200=942
/turbo_frame_tag_hello_world: Core 489.28 ▼31.9% (718.79) 10.83 ▲28.1% (8.45) 39.5 ▲120.1% (17.95) 200=14790
/manual_render_test: Core 672.27 ▲2.8% (654.01) 11.34 ▲21.1% (9.36) 21.2 ▲13.9% (18.62) 200=20308
/root_error_callbacks: Core 31.25 ▲6.2% (29.43) 268.12 ▼1.0% (270.78) 318.05 ▼2.4% (325.92) 200=950
/rails_form: Core 708.68 ▲7.9% (656.69) 8.57 ▼5.9% (9.1) 23.33 ▲28.9% (18.1) 200=21409

▲/▼ non-zero change vs baseline · 0.0% exact/near-zero match · 🔴 significant regression · 🟢 significant improvement (tracked measures) · (n) = baseline

@github-actions

Copy link
Copy Markdown
Contributor

Pro (shard 2/2) Benchmark Summary

Benchmark RPS p50(ms) p90(ms) Status
/empty: Pro 1082.91 ▼12.5% (1238.13) 5.3 ▼10.8% (5.94) 10.1 ▲3.7% (9.74) 200=32712
/ssr_shell_error: Pro 175.01 ▼46.1% (324.53) 41.38 ▲76.0% (23.51) 70.24 ▲81.5% (38.7) 200=5293
/ssr_sync_error: Pro 147.16 ▼53.9% (319.2) 27.32 ▲14.0% (23.96) 47.71 ▲17.3% (40.69) 200=4481
/rsc_component_error: Pro 143.54 ▼54.6% (316.0) 39.22 ▲69.8% (23.1) 57.32 ▲44.1% (39.79) 200=4369
/non_existing_stream_react_component: Pro 162.21 ▼52.6% (341.87) 28.16 ▲25.9% (22.37) 47.33 ▲28.0% (36.97) 200=4938
/server_side_redux_app_cached: Pro 343.65 ▼9.4% (379.43) 16.9 ▼9.7% (18.72) 45.84 ▲41.6% (32.38) 200=10390
/loadable: Pro 141.91 ▼52.6% (299.24) 41.24 ▲64.8% (25.03) 98.39 ▲143.9% (40.33) 200=4290
/apollo_graphql: Pro 133.53 ▼2.2% (136.55) 34.44 ▼32.4% (50.94) 56.31 ▼32.5% (83.44) 200=4063
/console_logs_in_async_server: Pro 3.16 ▲2.0% (3.1) 2119.96 ▼0.1% (2122.48) 2138.84 ▼2.8% (2200.38) 200=108
/stream_error_demo: Pro 2.9 ▼99.0% (304.85) 2012.28 ▲743.2% (238.65) 2036.85 ▲705.5% (252.86) 200=94
/stream_async_components: Pro 140.33 ▼55.7% (316.5) 32.33 ▲35.1% (23.93) 54.45 ▲37.9% (39.47) 200=4273
/rsc_posts_page_over_http: Pro 138.2 ▼55.6% (311.02) 42.87 ▲78.3% (24.05) 91.85 ▲122.7% (41.25) 200=4181
/rsc_echo_props: Pro 57.18 ▼73.0% (211.48) 120.3 ▲193.1% (41.05) 172.09 ▲171.4% (63.42) 200=1734
/client_side_fouc_probe: Pro 347.79 ▼8.0% (378.2) 16.26 ▼16.2% (19.39) 19.53 ▼34.1% (29.62) 200=10578
/async_on_server_sync_on_client_client_render: Pro 332.63 ▼5.6% (352.25) 17.22 ▼14.9% (20.25) 22.26 ▼34.3% (33.89) 200=10053
/server_router_client_render: Pro 401.86 ▲12.0% (358.69) 8.23 ▼58.9% (20.0) 25.79 ▼17.6% (31.28) 200=12223
/unwrapped_rsc_route_stream_render: Pro 175.92 ▼47.1% (332.75) 41.11 ▲81.5% (22.65) 62.9 ▲64.1% (38.33) 200=5317
/async_render_function_returns_component: Pro 194.93 ▼41.8% (334.93) 40.13 ▲71.5% (23.41) 64.48 ▲75.7% (36.69) 200=5859
/native_metadata: Pro 179.79 ▼44.1% (321.72) 19.16 ▼13.9% (22.25) 51.65 ▲20.4% (42.91) 200=5473
/hybrid_metadata_streaming: Pro 155.54 ▼51.8% (322.5) 37.1 ▲56.6% (23.69) 52.06 ▲29.6% (40.18) 200=4732
/cache_demo: Pro 129.2 ▼58.9% (314.35) 44.48 ▲76.8% (25.16) 95.01 ▲125.3% (42.17) 200=3908
/client_side_hello_world: Pro 338.2 ▼8.6% (370.16) 17.03 ▼14.5% (19.92) 25.75 ▼19.3% (31.92) 200=10218
/client_side_hello_world_shared_store_controller: Pro 376.96 ▲10.4% (341.38) 19.88 ▼4.1% (20.73) 33.97 ▲4.1% (32.63) 200=11390
/server_side_hello_world_shared_store: Pro 100.37 ▼61.8% (262.63) 57.72 ▲98.5% (29.08) 80.43 ▲76.7% (45.53) 200=3059
/server_side_hello_world_shared_store_defer: Pro 100.3 ▼63.0% (270.75) 57.61 ▲92.3% (29.96) 80.65 ▲78.5% (45.17) 200=3055
/server_side_hello_world_hooks: Pro 186.68 ▼43.8% (332.42) 41.63 ▲84.6% (22.55) 66.66 ▲78.7% (37.31) 200=5645
/server_side_log_throw: Pro 145.7 ▼55.4% (326.81) 39.62 ▲73.6% (22.82) 81.83 ▲117.1% (37.7) 200=4406
/server_side_log_throw_raise: Pro 210.01 ▼66.8% (633.27) 28.27 ▲126.2% (12.5) 69.68 ▲237.2% (20.66) 3xx=6351
/server_side_hello_world_es5: Pro 179.75 ▼44.5% (324.01) 41.27 ▲72.6% (23.91) 65.93 ▲78.2% (36.99) 200=5434
/server_side_hello_world_with_options: Pro 156.53 ▼50.8% (317.94) 37.45 ▲59.4% (23.49) 90.59 ▲137.0% (38.23) 200=4733
/client_side_manual_render: Pro 364.6 ▼1.6% (370.37) 13.23 ▼32.7% (19.67) 18.69 ▼40.1% (31.2) 200=11091
/react_router: Pro 177.07 ▼52.5% (373.0) 39.87 ▲100.8% (19.86) 59.12 ▲79.0% (33.02) 200=5356
/css_modules_images_fonts_example: Pro 178.68 ▼44.2% (320.35) 40.62 ▲69.8% (23.92) 60.82 ▲45.5% (41.8) 200=5401
/rendered_html: Pro 162.72 ▼49.8% (323.83) 28.21 ▲20.8% (23.35) 45.71 ▲19.9% (38.11) 200=4953
/react_helmet: Pro 111.07 ▼64.2% (310.49) 49.11 ▲86.6% (26.32) 67.78 ▲57.4% (43.06) 200=3388
/image_example: Pro 177.55 ▼45.0% (322.63) 30.24 ▲29.2% (23.4) 55.74 ▲47.1% (37.89) 200=5405
/posts_page: Pro 116.45 ▼48.2% (224.84) 49.33 ▲52.2% (32.4) 67.7 ▲34.1% (50.48) 200=3546

▲/▼ non-zero change vs baseline · 0.0% exact/near-zero match · 🔴 significant regression · 🟢 significant improvement (tracked measures) · (n) = baseline

@github-actions

Copy link
Copy Markdown
Contributor

Pro (shard 1/2) Benchmark Summary

Benchmark RPS p50(ms) p90(ms) Status
/: Pro 34.07 ▼80.0% (170.23) 166.76 ▲210.2% (53.76) 237.05 ▲201.7% (78.56) 200=1042
/error_scenarios_hub: Pro 337.89 ▼8.7% (370.03) 18.45 ▼2.0% (18.83) 31.57 ▲5.8% (29.85) 200=10211
/ssr_async_error: Pro 4.17 ▼98.7% (310.31) 2012.39 ▲743.2% (238.67) 2042.65 ▲705.5% (253.6) 200=136
/ssr_async_prop_error: Pro 1.4 ▼99.5% (301.28) 5017.83 ▲783.8% (567.79) 5361.57 ▲721.4% (652.72) 200=50
/non_existing_react_component: Pro 142.52 ▼58.2% (340.75) 50.11 ▲122.7% (22.5) 74.75 ▲103.7% (36.69) 200=4307
/non_existing_rsc_payload: Pro 131.91 ▼63.2% (358.79) 44.28 ▲102.2% (21.9) 84.59 ▲136.0% (35.84) 200=3989
/cached_react_helmet: Pro 348.82 ▼9.6% (385.89) 22.17 ▲19.8% (18.51) 36.01 ▲12.6% (31.98) 200=10538
/cached_redux_component: Pro 365.8 ▼6.9% (392.93) 20.93 ▲11.4% (18.79) 31.66 ▲2.6% (30.87) 200=11070
/lazy_apollo_graphql: Pro 114.0 ▼20.5% (143.36) 57.67 ▲14.8% (50.25) 87.58 ▲8.6% (80.64) 200=3456
/redis_receiver: Pro 86.78 ▲1.6% (85.39) 82.92 ▲24.9% (66.36) 167.63 ▲11.9% (149.74) 200=2626
/stream_shell_error_demo: Pro 120.41 ▼62.9% (324.92) 61.97 🔴 173.8% (22.64) 95.33 ▲140.8% (39.59) 200=3602,3xx=39
/test_incremental_rendering: Pro 2.9 ▼99.1% (310.62) 2010.73 ▲747.2% (237.34) 2078.91 ▲730.9% (250.19) 200=94
/rsc_posts_page_over_redis: Pro 86.8 ▼9.6% (95.96) 64.06 ▼12.2% (72.94) 110.71 ▼5.7% (117.4) 200=2643
/rsc_fouc_probe: Pro 136.65 ▼44.6% (246.69) 47.21 ▲47.4% (32.03) 75.78 ▲41.3% (53.64) 200=4133
/async_on_server_sync_on_client: Pro 2.2 ▼99.2% (293.25) 3011.67 ▲764.9% (348.21) 3060.24 ▲263.3% (842.25) 200=73
/server_router: Pro 133.17 ▼59.0% (324.99) 54.05 ▲131.4% (23.35) 89.61 ▲120.7% (40.6) 200=4027
/unwrapped_rsc_route_client_render: Pro 380.49 ▼0.9% (384.0) 19.52 ▲7.6% (18.14) 32.84 ▲10.6% (29.7) 200=11496
/async_render_function_returns_string: Pro 128.17 ▼61.4% (332.34) 44.55 ▲103.8% (21.86) 99.86 ▲171.9% (36.73) 200=3876
/async_components_demo: Pro 6.36 ▼96.7% (194.2) 1053.73 ▲634.4% (143.48) 1122.78 ▲591.3% (162.41) 200=202
/stream_native_metadata: Pro 142.26 ▼57.0% (331.17) 60.52 🔴 168.3% (22.56) 87.73 ▲127.5% (38.56) 200=4302
/rsc_native_metadata: Pro 7.69 ▼97.5% (306.92) 1012.31 ▲682.3% (129.39) 1025.24 ▲611.3% (144.14) 200=242
/react_intl_rsc_demo: Pro 73.96 ▼76.6% (316.6) 92.56 ▲248.3% (26.58) 141.87 ▲209.9% (45.78) 200=2239
/client_side_hello_world_shared_store: Pro 344.75 ▼3.0% (355.53) 21.74 ▲6.3% (20.45) 36.73 ▲11.4% (32.97) 200=10419
/client_side_hello_world_shared_store_defer: Pro 338.16 ▼2.4% (346.51) 22.15 ▲9.9% (20.16) 37.06 ▲17.5% (31.55) 200=10217
/server_side_hello_world_shared_store_controller: Pro 91.69 ▼66.5% (274.0) 84.47 🔴 192.6% (28.87) 122.32 ▲175.3% (44.43) 200=2774
/server_side_hello_world: Pro 140.57 ▼56.1% (320.34) 51.52 🔴 126.3% (22.77) 75.87 ▲96.0% (38.71) 200=4232,3xx=19
/client_side_log_throw: Pro 366.03 ▼4.0% (381.2) 20.21 ▲10.3% (18.32) 34.77 ▲14.5% (30.37) 200=11060
/server_side_log_throw_plain_js: Pro 381.98 ▼2.1% (390.0) 19.83 ▲5.9% (18.73) 31.34 ▲1.6% (30.83) 200=11543
/server_side_log_throw_raise_invoker: Pro 344.51 ▼19.6% (428.5) 16.01 ▼0.5% (16.09) 21.46 ▼18.3% (26.25) 200=10481
/server_side_redux_app: Pro 136.77 ▼58.3% (328.12) 52.66 ▲124.4% (23.47) 88.05 ▲131.6% (38.01) 200=4138
/server_side_redux_app_cached: Pro 370.12 ▼2.5% (379.43) 14.7 ▼21.5% (18.72) 28.87 ▼10.8% (32.38) 200=11257
/render_js: Pro 376.84 ▼3.0% (388.66) 16.64 ▼8.5% (18.19) 28.14 ▼6.9% (30.24) 200=11388
/pure_component: Pro 128.42 ▼61.8% (336.26) 35.46 ▲55.9% (22.75) 58.08 ▲49.0% (38.99) 200=3911
/turbolinks_cache_disabled: Pro 301.96 ▼22.4% (388.95) 18.41 ▼2.3% (18.84) 24.53 ▼17.5% (29.72) 200=9186
/xhr_refresh: Pro 87.14 ▼68.1% (273.29) 75.96 ▲172.3% (27.9) 112.67 ▲151.0% (44.9) 200=2638
/broken_app: Pro 148.42 ▼55.4% (332.67) 48.98 ▲110.0% (23.33) 79.66 ▲119.2% (36.35) 200=4488
/server_render_with_timeout: Pro 48.44 ▼84.9% (319.96) 118.3 ▲285.3% (30.7) 132.37 ▲194.1% (45.01) 200=1484

▲/▼ non-zero change vs baseline · 0.0% exact/near-zero match · 🔴 significant regression · 🟢 significant improvement (tracked measures) · (n) = baseline

@justin808

Copy link
Copy Markdown
Member Author

Merge Ledger Block: formal review decision still needed

Current head: 69251bdd4ae4efbf04e1457e128abf02eb994270

Closeout evidence refreshed after adversarial review:

  • GraphQL state: mergeStateStatus=CLEAN, unresolved review threads 0, reviewDecision=null.
  • GitHub checks are pass/skipped for the current head, including full CI and the newly requested benchmark suites.
  • Benchmark run: https://github.com/shakacode/react_on_rails/actions/runs/27582027490 passed Core benchmarks, Pro benchmarks (shard 1/2), Pro benchmarks (shard 2/2), and Pro Node Renderer benchmarks.
  • Local adversarial proof: PR tests fail against origin/main implementation and pass after restoring the PR implementation; focused Rspack/RSC Playwright FOUC spec passed locally (6/6).
  • Strict merge ledger artifact: /tmp/pr-batch-4031-4032-4009/pr-4047-merge-ledger-resume5.json.
  • Ledger status: complete_allowed: false only because pr.review_decision is UNKNOWN; P2 findings are marked fixed with evidence.

Maintainer action requested: submit a formal GitHub review decision for the current head. A regular PR comment such as “approved” still leaves GitHub reviewDecision null, so the strict ledger continues to block merge/readiness.

@justin808

Copy link
Copy Markdown
Member Author

Maintainer Waiver And Immediate Merge Decision

Current head: 69251bdd4ae4efbf04e1457e128abf02eb994270

Final live gate snapshot before merge:

  • Release mode: accelerated-rc via Release gate: react_on_rails 17.0.0 #3823.
  • Merge state: CLEAN.
  • Unresolved review threads: 0.
  • Checks: all current-head checks are pass/skipped, including full CI and benchmark suites.
  • Strict merge ledger: /tmp/pr-batch-4031-4032-4009/pr-4047-merge-ledger-final.json.
  • Ledger status: complete_allowed: false only because GitHub reviewDecision is UNKNOWN / null.

Final waiver set:

  • The formal non-author GitHub review object / reviewDecision gate is explicitly waived for this PR.
  • The maintainer approval already provided in the Codex session is accepted as the merge authorization for this PR.
  • Immediate merge is approved; do not wait for the default waiver-soak window.

Confidence note:

  • Validated: focused Jest/Ruby proof failed against the old implementation and passed with this PR implementation; focused Rspack/RSC Playwright FOUC spec passed locally (6/6); GitHub checks and benchmark suites passed for the current head.
  • Evidence: GitHub checks on this PR, benchmark run https://github.com/shakacode/react_on_rails/actions/runs/27582027490, local artifacts under /tmp/pr-batch-4031-4032-4009/adversarial-4047/.
  • UNKNOWN: GitHub reviewDecision remains null by waiver.
  • Residual risk: low; the change is release-sensitive, but the bug was reproduced without the fix and covered by focused unit/integration/E2E evidence plus benchmark CI.

@justin808 justin808 merged commit af6525d into main Jun 16, 2026
62 checks passed
@justin808 justin808 deleted the jg-codex/fix/fouc-reveal-gating-4031-4032 branch June 16, 2026 01:59
justin808 added a commit that referenced this pull request Jun 17, 2026
## Summary

Stamps the `17.0.0.rc.5` changelog header ahead of the release.

This collects the work merged since `v17.0.0.rc.4` (2026-06-14). A
classification sweep of all 14 merged PRs in `v17.0.0.rc.4..origin/main`
found exactly one user-visible change:

- **[Pro]** RSC and client-only FOUC reveal gating ([PR
4047](#4047)) — already
present under `[Unreleased]`, now moved under the new `17.0.0.rc.5`
header.

The other 13 PRs were all maintainer/agent tooling, CI-command
workflows, and docs (release-process / internal), so no new entries were
added.

## Changes

- Inserted `### [17.0.0.rc.5] - 2026-06-16` immediately after `###
[Unreleased]`.
- Moved the existing Pro FOUC reveal-gating entry under the new header.
- Updated compare links: `[unreleased]` → `v17.0.0.rc.5...main`; added
`[17.0.0.rc.5]` → `v17.0.0.rc.4...v17.0.0.rc.5`.
- Prior RC sections (`rc.4`…`rc.0`) and their compare links left intact
(RC sections are coalesced only at the stable release).

## Next step

After merge, run `rake release` (no args) — it reads `17.0.0.rc.5` from
CHANGELOG.md and auto-creates the GitHub release.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Documentation-only changelog and release-link updates with no
application code impact.
> 
> **Overview**
> Prepares **17.0.0.rc.5** (2026-06-16) in `CHANGELOG.md` by adding the
version section and moving the existing **[Pro] RSC and client-only FOUC
reveal gating** fix (PR 4047) out of `[Unreleased]`.
> 
> Compare links are updated so `[unreleased]` points at
`v17.0.0.rc.5...main` and a new `[17.0.0.rc.5]` link covers
`v17.0.0.rc.4...v17.0.0.rc.5`. No runtime or library code changes.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
b7ad049. 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

* **Chores**
* Updated changelog documentation to prepare for version 17.0.0.rc.5
release with updated version comparison references.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@justin808

Copy link
Copy Markdown
Member Author

Manual verification: reproduced the bug and confirmed the fix ✅

Reproduced via the merged jest regression suites (injectRSCPayload.test.ts, ClientSideRenderer.test.ts) using the pre-fix-on-one-file tactic. Bug (#4031/#4032): streamed RSC client-chunk stylesheets were injected as <link rel="preload" as="style"> hints (and not gated before reveal), so content revealed before its CSS applied → FOUC.

Reproduction

Squash-merged at af6525d8b, so pre-fix = af6525d8b~1. Reverted only src/injectRSCPayload.ts and src/ClientSideRenderer.ts, then restored.

cd packages/react-on-rails-pro && pnpm exec jest tests/injectRSCPayload.test.ts tests/ClientSideRenderer.test.ts --runInBand

Results

Scenario Streamed RSC CSS handling Result
Pre-fix source stays <link rel="preload" as="style">; not promoted/gated before reveal HTML 9 failed / 53 (BROKEN)
Post-fix restored promoted to <link rel="stylesheet" data-precedence="rsc-css"> and emitted before the reveal <div> 53 passed / 53 (FIXED)
# PRE-FIX (af6525d8b~1)
✕ promotes streamed RSC client chunk stylesheet preloads to gate reveal
    Expected: <link rel="stylesheet" ... data-precedence="rsc-css"/>
    Received: <link rel="preload" as="style" .../>      ← still just a preload hint, CSS not applied before reveal
✕ injects inferred RSC client chunk stylesheets before streamed reveal HTML   (stylesheetIndex < revealHtmlIndex fails)
✕ waits for inferred RSC client chunk stylesheets before flushing reveal HTML
   ... (9 FOUC-gating cases total)
Tests: 9 failed, 44 passed, 53 total

# POST-FIX (restored, git status clean)
Tests: 53 passed, 53 total

The 9 failures are exactly the CSS-before-reveal / preload-promotion gating cases.

Caveat

Mechanism-level against the real injectRSCPayload stream output and ClientSideRenderer in jsdom — it confirms the promoted <link rel="stylesheet"> is emitted before the reveal HTML and preload attributes/split-chunk handling are preserved. I did not run the Playwright E2E (rsc_fouc.spec.ts) in a real browser to observe zero visual flash, since that needs a built Pro dummy app + browser. Note this area has had revert churn (#3587#3860); this verification reflects net current behavior at HEAD, which now gates reveal on the promoted stylesheet.

justin808 added a commit that referenced this pull request Jun 22, 2026
… gate

The RSC FOUC ShakaPerf release gate detected the injected stylesheet with
`link[rel="stylesheet"][data-precedence="ror-rsc"]`, but the Pro RSC
renderer emits `data-precedence="rsc-css"`
(packages/react-on-rails-pro/src/injectRSCPayload.ts). The `ror-rsc`
wrapper this selector originally targeted (#3587) was reverted in #3860 and
replaced by the current `rsc-css` mechanism in #4047, so the selector matched
nothing and the gate's `hasStylesheet` assertion was silently inert.

Point the selector at the value the renderer actually emits and add a comment
pinning it to the source of truth so it does not drift again.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Follow-up: Fix RSC streamed component CSS reveal gating to prevent FOUC Follow-up: Fix client-only component reveal until CSS and JS are ready

1 participant