Skip to content

Add RSC FOUC acceptance coverage#4033

Merged
justin808 merged 1 commit into
mainfrom
jg-codex/rsc-fouc-acceptance-tests
Jun 15, 2026
Merged

Add RSC FOUC acceptance coverage#4033
justin808 merged 1 commit into
mainfrom
jg-codex/rsc-fouc-acceptance-tests

Conversation

@justin808

@justin808 justin808 commented Jun 14, 2026

Copy link
Copy Markdown
Member

Summary

Adds Pro dummy-app Playwright acceptance coverage for FOUC behavior under streamed RSC and client-only rendering. The tests assert browser-visible outcomes instead of implementation details:

  • streamed RSC probe pages under /rsc_fouc_probe
  • client-only probe pages under /client_side_fouc_probe
  • CSS sentinel fixtures proving required CSS is loaded while unused component CSS is not
  • network interception that holds CSS by inspecting response content for sentinel custom properties, not by guessing hashed filenames
  • JS-delay scenarios to prove behavior before hydration/application bundles are available

This PR intentionally does not change production runtime behavior. It adds acceptance tests plus pending markers for failures that expose the current FOUC bugs.

Research Basis

Test Outcomes

Active in CI:

  • client-only content does not appear while JS is delayed, even when CSS is ready
  • /rsc_fouc_probe and /client_side_fouc_probe load the matching rendered CSS sentinel and do not load --unused-fouc-probe-sentinel
  • e2e-test:rsc package-script guard now asserts e2e-tests/rsc_fouc.spec.ts stays in the RSC Playwright suite

Pending because they reveal existing user-visible bugs:

  • Pending #4032: streamed RSC content is visible and styled when its CSS is loaded, even while JS is delayed
  • Pending #4032: streamed RSC content does not appear before its CSS when JS is available
  • Pending #4032: streamed RSC content still waits for CSS if JS finishes first
  • Pending #4031: client-only content does not appear until CSS loads, even when JS is ready

Follow-ups:

No follow-up was opened for unrelated CSS loading because the minimal-CSS acceptance test passes.

Merge Rationale

This PR should be merged once CI and current-head review pass because it locks down the behavior Abanoub asked for without masking the known runtime defects. Passing tests stay active and guard against broad CSS-loading regressions. Failing behavior is preserved as opt-in pending coverage with linked issues and REACT_ON_RAILS_RUN_PENDING_FOUC_TESTS=true reproduction, so main stays green while the real FOUC bugs become mechanically reproducible.

Labels: full-ci, full-ci-no-benchmarks. This touches Pro dummy RSC/e2e coverage and the RSC test script, so broad CI is useful; benchmarks are not meaningful because this is test fixture coverage only and no production runtime code changes.

Agent Merge Confidence

Mode: accelerated-rc
Current head SHA: a5b27f1
Score: 9/10
Auto-merge recommendation: yes
Affected areas: RSC, Pro dummy app, Playwright e2e, package scripts
CI detector: script/ci-changes-detector origin/main -> React on Rails Pro Dummy app; recommended lint, Pro lint, Pro dummy integration, and Pro benchmarks. PR uses full-ci, full-ci-no-benchmarks; benchmark skips are expected because this is test fixture coverage only and does not change production runtime code.
Validation run:

  • Local: focused Playwright FOUC spec passed (2 passed, 4 skipped); opt-in pending run produced the expected existing-bug failures (4 failed, 2 passed); pnpm run build, build:test, build:test:rspack, e2e-test:rsc, pnpm run lint, pnpm start format.listDifferent, Pro RuboCop all passed; pnpm exec tsc --noEmit --pretty false only reports the pre-existing tests/strict-mode-support.test.tsx unknown-props errors.
  • GitHub checks: complete for a5b27f1; all current-head checks passed, with benchmark/docs/Claude-event skips explained by full-ci-no-benchmarks, path selection, or non-review event skips.
    Review/check gate:
  • Review threads: GraphQL unresolved count is 0 for a5b27f1.
  • Current-head reviewer verdicts:
    • Claude review: claude-review check succeeded for a5b27f1 at 2026-06-15T00:53:54Z; no confirmed blocker.
    • Cursor Bugbot: succeeded for a5b27f1.
    • CodeRabbit: status succeeded for a5b27f1.
  • Stale reviewer verdicts, advisory only:

Verification

  • pnpm run build -> passed
  • (cd react_on_rails_pro/spec/dummy && bundle exec rake react_on_rails:generate_packs) -> passed
  • (cd react_on_rails_pro/spec/dummy && pnpm run build:test) -> passed with existing warnings
  • started Pro dummy services for webpack test build:
    • (cd react_on_rails_pro/spec/dummy && pnpm run node-renderer)
    • (cd react_on_rails_pro/spec/dummy && RAILS_ENV=test bundle exec rails server -p 3000)
  • (cd react_on_rails_pro/spec/dummy && pnpm exec playwright test e2e-tests/rsc_fouc.spec.ts --project=chromium --reporter=line) -> passed, 2 passed, 4 skipped
  • (cd react_on_rails_pro/spec/dummy && REACT_ON_RAILS_RUN_PENDING_FOUC_TESTS=true pnpm exec playwright test e2e-tests/rsc_fouc.spec.ts --project=chromium --reporter=line) -> expected failures, 4 failed, 2 passed
  • (cd react_on_rails_pro/spec/dummy && pnpm run build:test:rspack) -> passed with existing warnings
  • started Pro dummy services for rspack test build and ran (cd react_on_rails_pro/spec/dummy && pnpm e2e-test:rsc) -> passed, 36 passed, 12 skipped
  • pnpm install --frozen-lockfile -> refreshed stale local node_modules to lockfile state, no tracked changes
  • cd react_on_rails_pro/spec/dummy && pnpm exec eslint e2e-tests/rsc_fouc.spec.ts tests/package-scripts.test.js -> passed
  • cd react_on_rails_pro/spec/dummy && pnpm exec jest tests/package-scripts.test.js -> passed, 7 passed
  • cd react_on_rails_pro/spec/dummy && pnpm exec tsc --noEmit --pretty false -> no errors from this PR; existing tests/strict-mode-support.test.tsx strict-mode errors remain
  • pnpm run lint -> passed
  • pnpm start format.listDifferent -> passed
  • (cd react_on_rails_pro && BUNDLE_GEMFILE=../Gemfile bundle exec rubocop --ignore-parent-exclusion) -> passed
  • pre-push hook after amended push -> branch Ruby RuboCop passed for pages_controller.rb and routes.rb
  • Post-review-fix validation:
    • cd react_on_rails_pro/spec/dummy && pnpm exec eslint e2e-tests/rsc_fouc.spec.ts -> passed
    • pnpm start format.listDifferent -> passed
    • (cd react_on_rails_pro/spec/dummy && pnpm exec playwright test e2e-tests/rsc_fouc.spec.ts --project=chromium --reporter=line) -> passed, 2 passed, 4 skipped
    • pnpm run lint -> passed after removing ignored node-renderer runtime bundles generated by the browser run
    • cd react_on_rails_pro/spec/dummy && pnpm exec tsc --noEmit --pretty false -> only existing tests/strict-mode-support.test.tsx strict-mode errors; no errors from this PR
    • latest review-fix pass for full-window hidden sampling:
      • cd react_on_rails_pro/spec/dummy && pnpm exec eslint e2e-tests/rsc_fouc.spec.ts -> passed
      • (cd react_on_rails_pro/spec/dummy && pnpm exec playwright test e2e-tests/rsc_fouc.spec.ts --project=chromium --reporter=line) -> passed, 2 passed, 4 skipped
      • pnpm start format.listDifferent -> passed
      • pnpm run lint -> passed
      • cd react_on_rails_pro/spec/dummy && pnpm exec tsc --noEmit --pretty false -> only existing tests/strict-mode-support.test.tsx strict-mode errors; no errors from this PR
    • pre-push hook after final amended push -> branch Ruby RuboCop passed for pages_controller.rb and routes.rb

Review Notes

Current head: a5b27f10f36a7d9a38e78b55ce6f896118deb37f.

codex review --base origin/main found one actionable TypeScript strictness issue in the new paint recorder. The branch was amended to fix that by using a local initialized paint array reference, and the diagnostic was rerun. A subsequent review rerun was stopped after it crawled ignored generated build artifacts; the generated artifacts were removed and no additional source finding had been reported.

Greptile P2 review comments were fixed and resolved: waitForTargetResponse() now resolves after route.fulfill(...), and explicit CSS-isolation pages are closed with try/finally.

Claude review comments were fixed and resolved: paint sampling now receives each probe sentinel explicitly, waitForTargetResponse() semantics match fulfillment timing, JS-delay tests release blocked scripts in finally, explicit pages close through try/finally, CSS sentinel waits have explicit 10s diagnostics, the no-visible window is named/documented as a deliberate 1s stability window, and the browser-only CSS isolation test no longer instantiates an unused page fixture.

Current-head Codex review comments were fixed and resolved: the RSC minimal-CSS page now aborts /rsc_payload/** so it cannot pass through the client fallback, the minimal-CSS assertions now prove each probe page excludes the opposite probe sentinel as well as the unused sentinel, and expectNoVisiblePaint() now samples for the full stability window instead of allowing an immediate negative-assertion pass.

Review churn: five follow-up review-fix pushes after the initial PR publication; each was locally validated before force-pushing with lease.

Agent coordination claim: agent-coord claim --repo shakacode/react_on_rails --target 4033 --branch jg-codex/rsc-fouc-acceptance-tests --agent-id codex-rsc-fouc-acceptance-tests --status pr-open-local-validated --ttl 14400.

Notes

Observed RSC bug evidence: the stream currently preloads the CSS chunk but swaps streamed content into the DOM before the stylesheet applies, so the probe can be visible with default computed styles or visible before the CSS response is released.

Observed client-only bug evidence: when JS is available and the CSS response is held, the client-only probe can become visible before its stylesheet is released.

Summary by CodeRabbit

  • New Features
    • Added two new FOUC probe pages to compare streamed React Server Components vs client-only rendering behavior.
    • Introduced dedicated probe UI components and corresponding styling for the new pages.
  • Tests
    • Added a Playwright e2e spec to validate probe reveal ordering and verify CSS/JS chunk loading behavior.
    • Updated the e2e test script and Jest assertions to include the new spec.

Note

Low Risk
Changes are limited to the Pro dummy app routes, fixtures, and e2e tests; production rendering behavior is untouched.

Overview
Adds Pro dummy-app Playwright acceptance coverage for flash-of-unstyled-content (FOUC) under streamed RSC (/rsc_fouc_probe) and client-only (/client_side_fouc_probe) rendering, without changing production runtime code.

New probe UI uses CSS custom-property sentinels and data-testid hooks so tests assert visible computed styles and timing (via requestAnimationFrame paint sampling), not internal implementation. rsc_fouc.spec.ts intercepts webpack CSS/JS by matching sentinel strings in response bodies (not hashed filenames), can delay or hold assets, and blocks /rsc_payload/** on the RSC isolation case so a client fallback cannot mask results.

CI-active cases include client-only content staying hidden while JS is delayed (CSS ready) and each probe route loading only its own CSS chunk (excluding an unused probe sentinel). Four scenarios that document current bugs (#4031/#4032) are marked test.fixme unless REACT_ON_RAILS_RUN_PENDING_FOUC_TESTS=true. e2e-test:rsc and package-scripts.test.js now include rsc_fouc.spec.ts.

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

@justin808 justin808 added full-ci full-ci-no-benchmarks Full CI test matrix but hard-suppress all benchmark suites (overrides detection + benchmark label) labels Jun 14, 2026
@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds FOUC (Flash of Unstyled Content) acceptance-test infrastructure to the dummy app. Two new Rails routes, controller actions, and ERB views expose RSC-streaming and client-only probe pages. Corresponding React components with CSS sentinel variables serve as fixtures. A 454-line Playwright spec intercepts CSS/JS delivery and asserts correct visibility timing for both rendering modes.

Changes

RSC & Client-Only FOUC Probe E2E Test Infrastructure

Layer / File(s) Summary
Routes, controller actions, and ERB views
react_on_rails_pro/spec/dummy/config/routes.rb, react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb, react_on_rails_pro/spec/dummy/app/views/pages/rsc_fouc_probe.html.erb, react_on_rails_pro/spec/dummy/app/views/pages/client_side_fouc_probe.html.erb
Two GET routes (rsc_fouc_probe, client_side_fouc_probe) are added with matching controller actions (streaming via stream_view_containing_react_components vs. normal render) and ERB templates calling stream_react_component and react_component respectively.
React probe components and CSS sentinel modules
react_on_rails_pro/spec/dummy/client/app/components/FoucProbe/ClientOnlyFoucProbeView.jsx, react_on_rails_pro/spec/dummy/client/app/components/FoucProbe/ClientOnlyFoucProbeView.module.scss, react_on_rails_pro/spec/dummy/client/app/components/FoucProbe/RscFoucProbeClient.jsx, react_on_rails_pro/spec/dummy/client/app/components/FoucProbe/RscFoucProbeClient.module.scss, react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RscFoucProbe.jsx, react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ClientOnlyFoucProbe.client.jsx, react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/UnusedFoucProbe.client.jsx, react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/UnusedFoucProbe.module.scss
Adds RscFoucProbeClient with 'use client' directive and --rsc-fouc-probe-sentinel CSS custom property, ClientOnlyFoucProbeView with --client-only-fouc-probe-sentinel, and UnusedFoucProbe with --unused-fouc-probe-sentinel as a negative-assertion fixture for CSS-chunk exclusivity tests. Includes auto-load wrappers RscFoucProbe.jsx (server component parent) and ClientOnlyFoucProbe.client.jsx for Rails component auto-loading.
Playwright spec: constants, types, and paint recording
react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts (prologue, constants, type definitions, recordProbePaints)
Defines shared constants (probe routes, test IDs, sentinel CSS variable strings, expected computed-style objects), TypeScript types for recorded paint samples and CSS/JS request controllers, environment gating, and recordProbePaints init-script that continuously samples element visibility and computed styles (background, border-top, color, sentinel CSS variable) into window.__reactOnRailsFoucProbePaints.
Playwright spec: assertion and interception helpers
react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts (test helpers and network interception)
Implements test assertion helpers (visiblePaints, expectNoVisiblePaint, waitWithTimeout, readProbeStyle, expectProbeStyled, blockClientRscPayloadFallback) for polling and validating probe visibility/styling; introduces network interception helpers controlCssBySentinel (buffers and optionally holds webpack test CSS responses until a target sentinel is detected) and delayCompiledJavaScript (blocks webpack test JS requests behind a release gate) to control resource delivery timing.
Playwright spec: test scenarios and coverage assertions
react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts (test suites and coverage test)
Creates test.describe suites exercising RSC and client-only visibility timing with gated CSS/JS delivery across multiple scenarios (several marked test.fixme for pending behavior). Adds coverage test that loads RSC and client-only probe routes in separate browser pages, records probe paint samples, intercepts webpack CSS/JS deliveries, verifies computed styling, and asserts each page loads only the CSS chunk(s) associated with its own sentinel while excluding sentinel strings for the other probe and unused fixture.
Test script and assertion updates
react_on_rails_pro/spec/dummy/package.json, react_on_rails_pro/spec/dummy/tests/package-scripts.test.js
Extends the e2e-test:rsc npm script to include e2e-tests/rsc_fouc.spec.ts in its Playwright test command, and updates the corresponding Jest assertion to verify the new spec is included.

Sequence Diagram(s)

sequenceDiagram
  participant Test as Playwright Test
  participant Page as Browser Page
  participant WebpackDev as Webpack Dev Server
  participant RailsApp as Rails App

  Test->>Page: navigate to /pages/rsc_fouc_probe or /pages/client_side_fouc_probe
  Page->>RailsApp: GET route → controller action → ERB view
  RailsApp-->>Page: HTML + streaming RSC or client-only component mount
  Test->>Page: addInitScript(recordProbePaints)
  Page->>Page: rAF loop samples computed styles and visibility → window.__reactOnRailsFoucProbePaints
  Test->>Page: controlCssBySentinel(sentinel) → intercept webpack CSS
  WebpackDev-->>Page: CSS buffered, held until cssController.release()
  Test->>Page: delayCompiledJavaScript() → intercept webpack JS
  WebpackDev-->>Page: JS buffered, held until jsController.release()
  Test->>Page: expectNoVisiblePaint(testId)
  Test->>Page: cssController.release()
  Page->>Page: CSS applied → computed styles updated
  Test->>Page: expectProbeStyled(testId, expectedStyle)
  Page-->>Test: poll readProbeStyle until visible + style matches sentinel
  Test->>Page: jsController.release()
  Page->>Page: JS hydrated → component finalized
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

  • shakacode/react_on_rails#3817: Introduced the e2e-test:rsc CI pipeline that this PR extends by adding e2e-tests/rsc_fouc.spec.ts to its Playwright test command.

Suggested labels

pending-spec

Suggested reviewers

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

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 'Add RSC FOUC acceptance coverage' accurately describes the main change: adding Playwright acceptance tests for FOUC behavior in RSC and client-only rendering scenarios.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
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/rsc-fouc-acceptance-tests

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 force-pushed the jg-codex/rsc-fouc-acceptance-tests branch from e050fb8 to 8236d61 Compare June 14, 2026 23:26
@justin808 justin808 marked this pull request as ready for review June 14, 2026 23:40
@justin808

Copy link
Copy Markdown
Member Author

+ci-status

@greptile-apps

greptile-apps Bot commented Jun 14, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds Playwright acceptance coverage for FOUC (Flash of Unstyled Content) behavior under streamed RSC and client-only rendering in the Pro dummy app. No production runtime code is changed.

Confidence Score: 4/5

Safe to merge — no production code is changed; all changes are test fixtures, probe components, routes, and the Playwright spec itself.

The two active tests pass CI and the four pending tests are correctly gated behind a feature-flag env var. The spec has a timing ambiguity where waitForTargetResponse settles before route.fulfill completes, and the CSS-isolation test does not close pages inside try/finally, but neither causes a false-positive result under the current test structure.

react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts — specifically the controlCssBySentinel timing and the cleanup path in the last test.

Important Files Changed

Filename Overview
react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts Core Playwright spec introducing CSS sentinel-based FOUC tests; contains a timing ambiguity in resolveTargetResponse vs route.fulfill sequencing and a missing try/finally in the CSS-isolation test.
react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb Adds two new controller actions (rsc_fouc_probe and client_side_fouc_probe) that delegate to stream_view_containing_react_components and render respectively — straightforward and consistent with existing patterns.
react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/UnusedFoucProbe.client.jsx Unused sentinel component intentionally registered in auto-load but never rendered on either probe page; provides the negative CSS-isolation assertion target.
react_on_rails_pro/spec/dummy/package.json Appends rsc_fouc.spec.ts to the e2e-test:rsc script, keeping the RSC suite guard accurate.
react_on_rails_pro/spec/dummy/tests/package-scripts.test.js Adds a Jest assertion that rsc_fouc.spec.ts is present in the e2e-test:rsc script, preventing future regressions where the spec is accidentally dropped from the RSC suite.

Sequence Diagram

sequenceDiagram
    participant T as Test
    participant PW as Playwright Route Handler
    participant B as Browser

    T->>PW: "controlCssBySentinel(holdTarget=true)"
    T->>B: page.goto(probe_path)
    B->>PW: "GET *.css"
    PW->>PW: fetch + read body
    PW->>T: resolveTargetRequest(url)
    Note over PW: await releaseGate (CSS held)
    T->>T: expectNoVisiblePaint
    T->>PW: releaseTarget()
    PW->>T: resolveTargetResponse(url) ← promise resolves HERE
    PW->>B: route.fulfill() ← CSS bytes arrive HERE
    T->>T: waitForTargetResponse() resolved
    T->>T: expectProbeStyled (polls 10s)
Loading

Reviews (1): Last reviewed commit: "Add RSC FOUC acceptance coverage" | Re-trigger Greptile

Comment thread react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts Outdated
Comment thread react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts Outdated
Comment thread react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts Outdated
Comment thread react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts
Comment thread react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts Outdated
Comment thread react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts
@claude

claude Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Review: RSC FOUC acceptance coverage

Overall this is a well-designed test PR. Using CSS custom properties as load sentinels (instead of guessing hashed filenames) is the right approach — it's resilient to bundler changes and makes intent explicit. The test.fixme pattern for documenting known FOUC bugs with linked issues is exactly right: it keeps main green while making defects mechanically reproducible.

Summary of findings

Four issues in rsc_fouc.spec.ts, all in the test file only (no production code is affected):

  1. recordProbePaints hardcodes sentinel property names (lines 108–110) — the closure checks --rsc-fouc-probe-sentinel and --client-only-fouc-probe-sentinel unconditionally for any testId. Adding a third probe type later would silently produce empty sentinel fields. See inline comment.

  2. waitForTargetResponse() resolves before route.fulfill() (lines 215–218) — resolveTargetResponse fires before the browser actually receives the response bytes, so the name implies a guarantee it doesn't provide. The 10-second expectProbeStyled poll covers this today, but the semantic is wrong and a future tight assertion would be intermittently flaky. Either move the resolve to after route.fulfill, or rename to clarify the pre-fulfillment timing. See inline comment.

  3. Active JS-delay test missing try/finally (lines 312–323) — the one currently-active test that blocks JS has no cleanup guard. If expectNoVisiblePaint throws, scripts.releaseScripts() is never called. The sibling 'streamed RSC content is visible and styled…' test already has the right try/finally pattern. See inline comment.

  4. Page leak on assertion failure in the CSS-chunking test (lines 340–369) — rscPage and clientPage are only closed in the happy path. If any assertion fails, or if waitForTargetResponse() hangs (it has no explicit timeout), both pages leak until the global Playwright timeout. Recommend try/finally around each page. See inline comment.

Minor observations (no action required)

  • trace: true in both probe ERB views will produce trace output in test runs; probably intentional for debugging but worth a comment if it's meant to stay.
  • client_side_fouc_probe.html.erb passes an explicit id: "ClientOnlyFoucProbe-react-component-0". This is fine for a dedicated probe page but is worth noting if there's a project convention around auto-generated IDs.
  • The e2e-test:rsc guard in tests/package-scripts.test.js is a nice defensive touch — locks down that the new spec stays in the suite.

@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 (2)
react_on_rails_pro/spec/dummy/client/app/components/FoucProbe/ClientOnlyFoucProbeView.jsx (1)

16-16: 💤 Low value

Optional: React import is not needed with modern JSX transform.

React 19 with the modern JSX transform (enabled per the upgrade guide) does not require importing React for JSX. You can safely remove this import.

♻️ Optional cleanup
-import React from 'react';
 import styles from './ClientOnlyFoucProbeView.module.scss';
🤖 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/client/app/components/FoucProbe/ClientOnlyFoucProbeView.jsx`
at line 16, The import statement for React at the top of
ClientOnlyFoucProbeView.jsx is unnecessary with React 19's modern JSX transform.
Remove the `import React from 'react';` line since the modern JSX transform
automatically handles JSX transformation without requiring an explicit React
import.
react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts (1)

57-76: ⚡ Quick win

Use interface for object shapes per coding guidelines.

The coding guidelines specify: "Prefer interface for defining object shapes in TypeScript" for **/*.{ts,tsx} files. ProbeStyle, CssController, and JavaScriptController are all pure object shapes and should use interface instead of type.

♻️ Refactor to use interfaces
-type ProbeStyle = {
+interface ProbeStyle {
   attached: boolean;
   backgroundColor?: string;
   borderTopColor?: string;
   color?: string;
   sentinel?: string;
   visible: boolean;
-};
+}

-type CssController = {
+interface CssController {
   loadedBodies: string[];
   releaseTarget: () => void;
   waitForTargetRequest: () => Promise<string>;
   waitForTargetResponse: () => Promise<string>;
-};
+}

-type JavaScriptController = {
+interface JavaScriptController {
   delayedRequests: string[];
   releaseScripts: () => void;
-};
+}
🤖 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/e2e-tests/rsc_fouc.spec.ts` around lines 57 -
76, Convert the three type aliases ProbeStyle, CssController, and
JavaScriptController to use the interface keyword instead of type. Replace the
type keyword and equals sign with the interface keyword, keeping the object
shape structure identical. This aligns with the coding guidelines that prefer
interfaces for defining object shapes in TypeScript files.

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
`@react_on_rails_pro/spec/dummy/client/app/components/FoucProbe/ClientOnlyFoucProbeView.jsx`:
- Line 16: The import statement for React at the top of
ClientOnlyFoucProbeView.jsx is unnecessary with React 19's modern JSX transform.
Remove the `import React from 'react';` line since the modern JSX transform
automatically handles JSX transformation without requiring an explicit React
import.

In `@react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts`:
- Around line 57-76: Convert the three type aliases ProbeStyle, CssController,
and JavaScriptController to use the interface keyword instead of type. Replace
the type keyword and equals sign with the interface keyword, keeping the object
shape structure identical. This aligns with the coding guidelines that prefer
interfaces for defining object shapes in TypeScript files.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d2dec073-9c6c-4693-928e-c1ba0770c0cc

📥 Commits

Reviewing files that changed from the base of the PR and between 3f78fe0 and 8236d61.

📒 Files selected for processing (15)
  • react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb
  • react_on_rails_pro/spec/dummy/app/views/pages/client_side_fouc_probe.html.erb
  • react_on_rails_pro/spec/dummy/app/views/pages/rsc_fouc_probe.html.erb
  • react_on_rails_pro/spec/dummy/client/app/components/FoucProbe/ClientOnlyFoucProbeView.jsx
  • react_on_rails_pro/spec/dummy/client/app/components/FoucProbe/ClientOnlyFoucProbeView.module.scss
  • react_on_rails_pro/spec/dummy/client/app/components/FoucProbe/RscFoucProbeClient.jsx
  • react_on_rails_pro/spec/dummy/client/app/components/FoucProbe/RscFoucProbeClient.module.scss
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ClientOnlyFoucProbe.client.jsx
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RscFoucProbe.jsx
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/UnusedFoucProbe.client.jsx
  • react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/UnusedFoucProbe.module.scss
  • react_on_rails_pro/spec/dummy/config/routes.rb
  • react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts
  • react_on_rails_pro/spec/dummy/package.json
  • react_on_rails_pro/spec/dummy/tests/package-scripts.test.js

@justin808 justin808 force-pushed the jg-codex/rsc-fouc-acceptance-tests branch from 8236d61 to dc9c99c Compare June 14, 2026 23:53
@claude

claude Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Review: FOUC acceptance tests for RSC and client-only rendering

Good addition overall. The CSS sentinel approach is robust, the test.fixme pattern with linked issues is the right way to gate known-failing assertions, and the interceptor logic in controlCssBySentinel is clean. No production code is touched.

Four issues worth addressing before merge:

  1. Hardcoded sentinels in sample() (line 108-110): the sample loop always tries both --rsc-fouc-probe-sentinel and --client-only-fouc-probe-sentinel regardless of which testId is being sampled. Adding a third probe later requires updating sample() separately from the test ID list — a silent maintenance gap. Threading the sentinel name alongside each testId (or keying a map by test ID) would remove the coupling.

  2. waitForTargetRequest/waitForTargetResponse can hang indefinitely (lines 227-228): if the build changes so that no CSS chunk contains the expected sentinel, these Promises never settle and the test only fails as a timeout with no actionable message. Wrapping them with Promise.race([targetRequest, timeout(10000, 'sentinel not found in any CSS chunk')]) would produce a clear failure immediately.

  3. expectNoVisiblePaint uses a 1-second hardcoded timeout (line 132): every other timeout in this file uses 10000 ms. On a slow CI worker where CSS is just delayed, 1 s could produce a false green. Consider aligning with the 10 s budget, or at least adding a comment explaining why 1 s is intentional here.

  4. beforeEach opens a page that the CSS-chunk-isolation test never uses (lines 252-254): that test takes { browser } and creates its own pages, so the fixture page from beforeEach is instantiated but discarded. Extracting the isolation test into a separate test.describe block without the beforeEach would avoid the wasted allocation.

Minor / non-blocking: UnusedFoucProbe collocates its view and SCSS in ror-auto-load-components/ while the other two probes keep those files under components/FoucProbe/. Harmless, but inconsistent with the established pattern.

Comment thread react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts Outdated
Comment thread react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts Outdated
Comment thread react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts
@justin808 justin808 force-pushed the jg-codex/rsc-fouc-acceptance-tests branch from dc9c99c to 446c43e Compare June 14, 2026 23:56
Comment thread react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts Outdated
@justin808 justin808 force-pushed the jg-codex/rsc-fouc-acceptance-tests branch from 446c43e to ee0a290 Compare June 15, 2026 00:01

@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: ee0a29098f

ℹ️ 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 react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts
Comment thread react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts Outdated
@justin808

Copy link
Copy Markdown
Member Author

+ci-run-full

Comment thread react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts
Comment thread react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts
Comment thread react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts
Comment thread react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts
Comment thread react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts
@claude

claude Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Code Review

Overall the FOUC acceptance harness is well-designed. The sentinel-based CSS interception (inspecting response bodies for CSS custom properties rather than guessing content-hashed filenames) is a robust approach, and the active/pending split with linked issue numbers is a clear way to preserve failing coverage without breaking CI.

However, comparing the current branch state to what the PR description lists as "fixed in review," four of those fixes appear to have been accidentally reverted in one of the amendment rounds. All four are in rsc_fouc.spec.ts:

# Issue Where
1 resolveTargetResponse fires before route.fulfill() — tests pass only because assertions use polling, but the function name is now semantically wrong and any single-shot CSS-applied check added later could get a false positive line 214
2 recordProbePaints hardcodes both sentinel property names and OR-chains them instead of reading the per-probe sentinel passed by the caller lines 108–109
3 CSS isolation test closes pages with bare await page.close() rather than try/finally — if any RSC assertion throws, rscPage leaks and the clientPage block never runs lines 340–367
4 expectNoVisiblePaint uses magic number 1000 — the PR description references a NO_VISIBLE_STABLE_WINDOW_MS constant but it was not carried forward into the final amended version line 131

There is also a minor robustness issue in the active (non-pending) test at line 311: scripts.releaseScripts() is not in a finally block, so an unexpected failure in expectNoVisiblePaint would leave JS blocked and cause expectProbeStyled to time out rather than surfacing the original failure (inline comment at line 311).

No security concerns and no production code is changed, so the blast radius is test-only. The issues above are worth a fix pass before merging since several of them were explicitly called out as already fixed in the review notes.

@justin808 justin808 force-pushed the jg-codex/rsc-fouc-acceptance-tests branch from ee0a290 to a3696fb Compare June 15, 2026 00:22

@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: a3696fb617

ℹ️ 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 react_on_rails_pro/spec/dummy/e2e-tests/rsc_fouc.spec.ts Outdated
@justin808 justin808 force-pushed the jg-codex/rsc-fouc-acceptance-tests branch from a3696fb to a5b27f1 Compare June 15, 2026 00:39
@justin808 justin808 merged commit 6189641 into main Jun 15, 2026
32 checks passed
@justin808 justin808 deleted the jg-codex/rsc-fouc-acceptance-tests branch June 15, 2026 01:41
justin808 added a commit that referenced this pull request Jun 15, 2026
…e-demo-cost-docs

* origin/main:
  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)
  Allow Pro RSC peer check for React 19.2 (#4026)
  Split llms-full.txt into OSS and Pro tiers to clear the 2048 KiB gate (#4021)
  Codify maintainer attention contract (#3987)
  Docs: correct vm.Script caching analysis caveats (#3997)
  CI: handle unavailable PR head repo for +ci-run-full (#3986)
  Fix Pro dummy lockfile drift and rich text demo (#3989)
  Add FOUC integration tests for generated CSS (#4005)
  Make per-PR benchmarks opt-in and trim per-route warm-up (#4012) (#4013)
  Treat docs-internal/ tree as documentation in CI change detection (#4016)
  Add issue triage prompt skill (#3983)
  Point coordination docs at agent-coord bootstrap (#4008)
  Docs: backfill async RSC manifest changelog entry (#3993)
  Migrate conductor.json to .conductor/settings.toml (#4007)
  Bump react-hooks lint to v6 and document RSC compiler boundary (#3963)
justin808 added a commit that referenced this pull request Jun 15, 2026
* 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
justin808 added a commit that referenced this pull request Jun 15, 2026
* 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
justin808 added a commit that referenced this pull request Jun 15, 2026
…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
justin808 added a commit that referenced this pull request Jun 15, 2026
…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
justin808 added a commit that referenced this pull request Jun 15, 2026
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

full-ci full-ci-no-benchmarks Full CI test matrix but hard-suppress all benchmark suites (overrides detection + benchmark label)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant