Skip to content

Conversation

@justin808
Copy link
Member

@justin808 justin808 commented Nov 11, 2025

Summary

Fixes Playwright E2E test timeouts in Pro dummy app by applying the same defer loading strategy fix from #1972 (commit d1a8a1a).

Problem

The Playwright E2E tests for streaming were timing out after 30 seconds, waiting for a console message "ToggleContainer with title" that never appeared. This indicated that React components weren't hydrating properly.

Root Cause

The Pro dummy app was still using async: true for javascript_pack_tag while the open-source dummy app was updated to defer: true in commit d1a8a1a (#1972). This created a race condition where:

  1. Generated component packs load asynchronously
  2. Main client-bundle also loads asynchronously
  3. If client-bundle executes before component registrations complete, React tries to hydrate unregistered components
  4. ToggleContainer never hydrates → useEffect never runs → console.log never fires → test times out

Changes

  1. Changed javascript_pack_tag from async: true to defer: true in application.html.erb
  2. Added precompile_hook to shakapacker.yml for automatic pack generation
  3. Added bin/shakapacker-precompile-hook script for ReScript builds and pack generation

Why This Fixes It

Using defer: true ensures proper script execution order:

  • Generated component packs load and register components before main bundle executes
  • Prevents race condition
  • Components hydrate properly
  • Console messages fire as expected
  • Tests pass

Test Plan

  • Verify Playwright E2E tests pass in CI
  • Confirm ToggleContainer hydrates and logs console message
  • Check all 11 streaming tests complete successfully

Related

🤖 Generated with Claude Code


This change is Reviewable

Summary by CodeRabbit

  • Bug Fixes

    • Switched script loading from async to defer so component registration completes before React hydration, improving initialization reliability.
  • New Features

    • Added a precompile hook system to run pre-build tasks (conditional language builds and bundle generation) with logging, error handling, and controlled exit codes.
    • Added a configuration option to specify the precompile hook script for pre-build customization and security control.
  • Documentation

    • Updated guidance to recommend using defer for pack loading to ensure correct registration timing.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 11, 2025

Walkthrough

Replaced async with defer for the client bundle in the dummy layout; added a Shakapacker precompile hook script that optionally builds ReScript and conditionally runs react_on_rails pack generation; added precompile_hook config entry to the dummy Shakapacker YAML.

Changes

Cohort / File(s) Summary
Layout script loading
react_on_rails_pro/spec/dummy/app/views/layouts/application.html.erb
Switched javascript_pack_tag option from async: true to defer: true and updated the inline comment to describe defer-based registration/hydration ordering.
Shakapacker precompile hook (new)
react_on_rails_pro/spec/dummy/bin/shakapacker-precompile-hook
Added new Ruby hook with find_rails_root, build_rescript_if_needed, and generate_packs_if_needed; detects Rails root, optionally runs yarn/npm to build ReScript if config files exist, conditionally invokes react_on_rails:generate_packs, logs errors, and returns appropriate exit codes.
Shakapacker config
react_on_rails_pro/spec/dummy/config/shakapacker.yml
Added precompile_hook: 'bin/shakapacker-precompile-hook' under default config with comments about running trusted precompile hooks.
Initializer comment only
react_on_rails_pro/spec/dummy/config/initializers/react_on_rails.rb
Added comments advising not to override generated_component_packs_loading_strategy (rely on default :defer); no behavioral change.

Sequence Diagram(s)

sequenceDiagram
    participant Shakapacker as Shakapacker
    participant Hook as bin/shakapacker-precompile-hook
    participant FS as Filesystem
    participant Node as Yarn/NPM
    participant Rails as Rails env
    participant ROR as react_on_rails:generate_packs

    Shakapacker->>Hook: invoke precompile hook
    Hook->>FS: find_rails_root (traverse up to config/environment.rb)
    alt ReScript config present (bsconfig.json or rescript.json)
        Hook->>Node: run yarn or npm build:rescript
        Node-->>Hook: success / failure
    end
    Hook->>Rails: load environment (if needed) and check initializer flags
    alt auto_load_bundle or components_subdirectory enabled
        Hook->>ROR: run react_on_rails:generate_packs
        ROR-->>Hook: success / failure
    end
    alt error
        Hook->>Shakapacker: exit 1 (log error & backtrace)
    else
        Hook->>Shakapacker: exit 0
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Areas for extra attention:
    • correctness and edge cases of find_rails_root upward traversal
    • subprocess invocation and shell/security handling for yarn/npm
    • detection logic for initializer flags controlling pack generation
    • exit codes and error/logging behavior in the new hook script

Possibly related PRs

Suggested reviewers

  • AbanoubGhadban

Poem

🐰 I hopped through code at break of dawn,

Swapped async to defer — now scripts load on,
I sniffed for Rails roots with careful pace,
Built ReScript where configs left a trace,
Packs rolled out gently — a tidy little race 🌱

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: fixing Playwright test timeouts by switching to defer loading strategy, which is the core objective of the PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch justin808/playwright-streaming-timeout

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.

@claude
Copy link

claude bot commented Nov 11, 2025

Code Review: Fix Pro dummy app Playwright test timeouts

✅ Overall Assessment

This is a well-structured fix that correctly addresses the Playwright test timeout issue by aligning the Pro dummy app with the open-source configuration from PR #1972. The changes are consistent, well-documented, and follow established patterns.


📋 Summary of Changes

  1. Changed javascript_pack_tag from async: true to defer: true in application.html.erb
  2. Added precompile_hook configuration to shakapacker.yml
  3. Added bin/shakapacker-precompile-hook script (executable)

✅ Strengths

1. Root Cause Analysis

2. Code Quality

  • Ruby best practices: Uses array form for system() calls for security (lines 76, 80)
  • Cross-platform support: Checks for both yarn and npm availability
  • Defensive programming: Multiple existence checks before proceeding
  • Error handling: Proper exception handling with backtrace output
  • RuboCop compliance: Includes rubocop directives for justified complexity

3. Documentation

  • Comprehensive inline comments explaining the "why" not just the "what"
  • Updated comments in application.html.erb to reflect the new strategy
  • Security warning in shakapacker.yml about precompile hook path

4. Consistency

  • Matches the open-source dummy app structure
  • Uses the same precompile hook script pattern
  • Aligns with established project conventions

🔍 Detailed Review

application.html.erb (lines 24-31)

Good: Updated comment accurately describes the defer strategy purpose
Good: Maintains the skip_js_packs test parameter
⚠️ Minor observation: The Pro version has more elaborate layout structure than open-source (includes CSS pack tags and more metadata), but this is expected for Pro features

shakapacker.yml (lines 22-25)

Excellent: Includes security warning about precompile hook
Good: Follows the same configuration pattern as open-source
Good: Uses relative path from project root

bin/shakapacker-precompile-hook (entire file)

Excellent: Robust implementation with multiple safety checks
Security: Uses array form for system calls to prevent shell injection
Cross-platform: Handles both yarn and npm
Fail-fast: Exits with error code 1 on failures
Good UX: Uses emoji indicators for status messages
Smart detection: Regex pattern properly excludes commented config lines (line 65-66)

Specific highlights:

  • Line 31-32: Proper use of File::NULL for silent checks
  • Line 65-66: Regex correctly matches only active config (not comments)
  • Line 76: Uses IO.popen with array form and proper error redirection
  • Line 92-101: Clean error handling in main execution block

🐛 Potential Issues

None identified - The code is production-ready.


🔒 Security Review

Command injection prevention: Uses array form for all system() and IO.popen() calls
Path traversal: Uses File.join() for path construction
Input validation: Checks file existence before reading
Secure defaults: Returns early if conditions not met
Documentation: Includes security warning in YAML config


⚡ Performance Considerations

Minimal overhead: Early returns prevent unnecessary work
Efficient checks: Uses simple file existence tests before complex operations
Parallel downloads: defer: true maintains parallel script downloads while ensuring execution order


🧪 Test Coverage

Test Plan Status: ⚠️ Checklist items are unchecked in PR description

Recommendations for testing:

  1. ✅ Verify Playwright E2E tests pass in CI (should be automatic)
  2. ✅ Confirm ToggleContainer hydrates and logs console message
  3. ✅ Check all 11 streaming tests complete successfully
  4. Consider adding a test that specifically verifies component registration order
  5. Test both development and production builds with the new precompile hook

💡 Suggestions

1. Test Plan Completion
Update the PR description to check off test plan items once CI passes.

2. Verification Script (optional)
Consider adding a simple verification that the hook executes correctly:

# In CI or locally
RAILS_ENV=test bin/shakapacker-precompile-hook
echo "Hook exit code: $?"

3. Documentation (optional, future work)
Consider documenting this pattern in the main React on Rails docs, as the defer vs async strategy is critical for auto_load_bundle users.


📝 Checklist

  • ✅ Code follows project conventions (CLAUDE.md)
  • ✅ RuboCop compliant (has proper directives)
  • ✅ Security best practices followed
  • ✅ Cross-platform compatibility
  • ✅ Error handling in place
  • ✅ Comments explain "why" not just "what"
  • ✅ Consistent with related changes (PR Fix component registration race condition with defer loading strategy #1972)
  • ⚠️ Test plan needs verification (wait for CI)

🎯 Recommendation

LGTM after CI passes

This is a solid fix that:

  • Correctly diagnoses and resolves the root cause
  • Follows established patterns from the open-source codebase
  • Includes proper security considerations
  • Has excellent documentation

Once the Playwright tests pass in CI, this is ready to merge.


📚 References


Review generated with assistance from Claude Code

@coderabbitai coderabbitai bot added the bug label Nov 11, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
react_on_rails_pro/spec/dummy/bin/shakapacker-precompile-hook (1)

72-78: Surface skip reason when Bundler is unavailable. Right now we return silently if bundle_available is false (or later when the task is missing), so folks won’t know packs were skipped. Consider emitting a warn before those early returns to make the hook’s behavior explicit during CI troubleshooting.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad7c916 and 5cd3cc6.

📒 Files selected for processing (3)
  • react_on_rails_pro/spec/dummy/app/views/layouts/application.html.erb (1 hunks)
  • react_on_rails_pro/spec/dummy/bin/shakapacker-precompile-hook (1 hunks)
  • react_on_rails_pro/spec/dummy/config/shakapacker.yml (1 hunks)
🧰 Additional context used
🧠 Learnings (10)
📓 Common learnings
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.
Learnt from: justin808
Repo: shakacode/react_on_rails PR: 1770
File: lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx:2-2
Timestamp: 2025-09-16T08:01:11.146Z
Learning: React on Rails uses webpack CSS Modules configuration with namedExports: true, which requires the import syntax `import * as style from './file.module.css'` rather than the default export pattern. This configuration enables better tree shaking and bundle size optimization for CSS modules.
Learnt from: alexeyr-ci2
Repo: shakacode/react_on_rails PR: 1732
File: spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx:40-44
Timestamp: 2025-04-26T21:55:55.874Z
Learning: In the react_on_rails project, files under `app-react16` directories are copied/moved to corresponding `/app` directories during the conversion process (removing the `-react16` suffix), which affects their relative import paths at runtime.
Learnt from: Romex91
Repo: shakacode/react_on_rails PR: 1697
File: package-scripts.yml:28-28
Timestamp: 2025-02-12T16:38:06.537Z
Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.
Learnt from: theforestvn88
Repo: shakacode/react_on_rails PR: 1620
File: spec/dummy/client/app/startup/HelloTurboStream.jsx:3-3
Timestamp: 2024-07-27T10:08:35.868Z
Learning: The `RailsContext` import in `spec/dummy/client/app/startup/HelloTurboStream.jsx` is used later in the project, as clarified by the user theforestvn88.
Learnt from: theforestvn88
Repo: shakacode/react_on_rails PR: 1620
File: spec/dummy/client/app/startup/HelloTurboStream.jsx:3-3
Timestamp: 2024-10-08T20:53:47.076Z
Learning: The `RailsContext` import in `spec/dummy/client/app/startup/HelloTurboStream.jsx` is used later in the project, as clarified by the user theforestvn88.
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1745
File: node_package/src/RSCRequestTracker.ts:8-14
Timestamp: 2025-07-08T05:57:29.630Z
Learning: The global `generateRSCPayload` function in React on Rails Pro (RORP) is provided by the framework during rendering requests, not implemented in application code. The `declare global` statements are used to document the expected interface that RORP will inject at runtime.
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/clientStartup.ts:18-21
Timestamp: 2025-02-13T16:50:47.848Z
Learning: In the react_on_rails module, the `reactOnRailsPageUnloaded` function in clientStartup.ts is intentionally kept private as it's only used internally as a callback for `onPageUnloaded`.
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1696
File: node_package/src/RSCPayloadContainer.ts:0-0
Timestamp: 2025-04-09T12:56:10.756Z
Learning: In the react_on_rails codebase, RSC payloads are already stringified using `JSON.stringify()` before being processed by the `escapeScript` function, which handles escaping of special characters. The function only needs to handle specific HTML markers like comments and closing script tags.
📚 Learning: 2025-09-16T08:01:11.146Z
Learnt from: justin808
Repo: shakacode/react_on_rails PR: 1770
File: lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx:2-2
Timestamp: 2025-09-16T08:01:11.146Z
Learning: React on Rails uses webpack CSS Modules configuration with namedExports: true, which requires the import syntax `import * as style from './file.module.css'` rather than the default export pattern. This configuration enables better tree shaking and bundle size optimization for CSS modules.

Applied to files:

  • react_on_rails_pro/spec/dummy/app/views/layouts/application.html.erb
  • react_on_rails_pro/spec/dummy/bin/shakapacker-precompile-hook
  • react_on_rails_pro/spec/dummy/config/shakapacker.yml
📚 Learning: 2025-04-26T21:55:55.874Z
Learnt from: alexeyr-ci2
Repo: shakacode/react_on_rails PR: 1732
File: spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx:40-44
Timestamp: 2025-04-26T21:55:55.874Z
Learning: In the react_on_rails project, files under `app-react16` directories are copied/moved to corresponding `/app` directories during the conversion process (removing the `-react16` suffix), which affects their relative import paths at runtime.

Applied to files:

  • react_on_rails_pro/spec/dummy/app/views/layouts/application.html.erb
  • react_on_rails_pro/spec/dummy/bin/shakapacker-precompile-hook
📚 Learning: 2025-02-12T16:38:06.537Z
Learnt from: Romex91
Repo: shakacode/react_on_rails PR: 1697
File: package-scripts.yml:28-28
Timestamp: 2025-02-12T16:38:06.537Z
Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.

Applied to files:

  • react_on_rails_pro/spec/dummy/app/views/layouts/application.html.erb
  • react_on_rails_pro/spec/dummy/bin/shakapacker-precompile-hook
📚 Learning: 2024-07-27T10:08:35.868Z
Learnt from: theforestvn88
Repo: shakacode/react_on_rails PR: 1620
File: spec/dummy/client/app/startup/HelloTurboStream.jsx:3-3
Timestamp: 2024-07-27T10:08:35.868Z
Learning: The `RailsContext` import in `spec/dummy/client/app/startup/HelloTurboStream.jsx` is used later in the project, as clarified by the user theforestvn88.

Applied to files:

  • react_on_rails_pro/spec/dummy/app/views/layouts/application.html.erb
📚 Learning: 2025-09-15T21:24:48.207Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.

Applied to files:

  • react_on_rails_pro/spec/dummy/app/views/layouts/application.html.erb
📚 Learning: 2025-07-08T05:57:29.630Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1745
File: node_package/src/RSCRequestTracker.ts:8-14
Timestamp: 2025-07-08T05:57:29.630Z
Learning: The global `generateRSCPayload` function in React on Rails Pro (RORP) is provided by the framework during rendering requests, not implemented in application code. The `declare global` statements are used to document the expected interface that RORP will inject at runtime.

Applied to files:

  • react_on_rails_pro/spec/dummy/app/views/layouts/application.html.erb
  • react_on_rails_pro/spec/dummy/bin/shakapacker-precompile-hook
📚 Learning: 2025-02-13T16:50:26.861Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/turbolinksUtils.ts:34-36
Timestamp: 2025-02-13T16:50:26.861Z
Learning: In React on Rails, when checking for Turbolinks version 5 using `turbolinksVersion5()`, always ensure `Turbolinks` exists first by checking `turbolinksInstalled()` to prevent TypeError when accessing properties.

Applied to files:

  • react_on_rails_pro/spec/dummy/app/views/layouts/application.html.erb
📚 Learning: 2025-02-13T16:50:47.848Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/clientStartup.ts:18-21
Timestamp: 2025-02-13T16:50:47.848Z
Learning: In the react_on_rails module, the `reactOnRailsPageUnloaded` function in clientStartup.ts is intentionally kept private as it's only used internally as a callback for `onPageUnloaded`.

Applied to files:

  • react_on_rails_pro/spec/dummy/app/views/layouts/application.html.erb
📚 Learning: 2025-04-09T12:56:10.756Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1696
File: node_package/src/RSCPayloadContainer.ts:0-0
Timestamp: 2025-04-09T12:56:10.756Z
Learning: In the react_on_rails codebase, RSC payloads are already stringified using `JSON.stringify()` before being processed by the `escapeScript` function, which handles escaping of special characters. The function only needs to handle specific HTML markers like comments and closing script tags.

Applied to files:

  • react_on_rails_pro/spec/dummy/app/views/layouts/application.html.erb
🔇 Additional comments (2)
react_on_rails_pro/spec/dummy/app/views/layouts/application.html.erb (1)

27-33: Defer attribute restores hydration order. Switching to defer mirrors the OSS dummy fix and guarantees component packs register before hydration—looks solid.

react_on_rails_pro/spec/dummy/config/shakapacker.yml (1)

22-25: Hook config lines up with the automation. Wiring the precompile hook here cleanly connects the new script into pack generation flow.

@claude
Copy link

claude bot commented Nov 11, 2025

Code Review: PR #1977

Overview

This PR successfully addresses Playwright E2E test timeouts in the Pro dummy app by aligning with the defer loading strategy fix from PR #1972. The changes are well-documented and the root cause analysis is thorough.


✅ Strengths

1. Excellent Problem Analysis

2. Consistent Implementation

The bin/shakapacker-precompile-hook script is identical between open-source and Pro dummy apps, ensuring consistency across the monorepo. This is excellent for maintainability.

3. Robust Hook Script

The precompile hook demonstrates several best practices:

  • Cross-platform compatibility (yarn/npm detection)
  • Security-conscious (uses array form for system calls)
  • Graceful degradation (silent returns when features aren't needed)
  • Proper error handling with exit codes
  • Good logging with emoji indicators

4. Security Awareness

The shakapacker.yml includes a security comment about the precompile hook, which is important for preventing malicious hook injection.


🔍 Code Quality Observations

Precompile Hook Script (bin/shakapacker-precompile-hook)

Positives:

  • Clean separation of concerns with dedicated functions
  • Appropriate RuboCop disables with justification
  • Good comments explaining the rationale

Minor Suggestions:

  1. Regex Complexity (lines 65-66): The regex /^\s*(?!#).*config\.auto_load_bundle\s*=/ uses negative lookahead which is correct, but could be more precise:

    # Current approach might match commented lines with leading whitespace
    # Consider: /^\s*[^#]*config\.auto_load_bundle\s*=/
    # Or even simpler: check if line.strip starts with '#'

    However, the current implementation should work fine in practice.

  2. File.read Performance: Reading the entire initializer file (line 63) is fine for this use case, but be aware this loads the full file into memory. For very large files, consider line-by-line processing (though unlikely to be an issue here).

  3. Error Messages: Consider adding the Rails root path to error messages for easier debugging:

    warn "❌ Pack generation failed in #{rails_root}"

View Template Changes (application.html.erb)

Positives:

  • Clear comments explaining the rationale for defer: true
  • Maintains the skip_js_packs testing parameter

Observation:

  • The comment accurately explains that defer ensures proper script execution order for generated component packs

🎯 Test Coverage Considerations

Strong Points:

  • The PR targets the exact failing test scenario (line 52 in streaming.spec.ts)
  • Test waits for console message 'ToggleContainer with title' which directly validates component hydration

Recommendations:

  1. Add Regression Test: Consider adding a specific test case that validates script execution order, perhaps by:

    • Checking that component registrations complete before hydration
    • Verifying no "component not registered" errors in console
    • Testing with multiple generated component packs
  2. Test Hook Execution: Add tests for the precompile hook script itself:

    # Test that hook runs successfully
    # Test with/without ReScript config
    # Test with/without React on Rails config

⚡ Performance Considerations

Defer vs Async:

  • Correct choice: defer provides deterministic execution order while maintaining parallel downloads
  • No performance regression: Scripts still download in parallel, only execution order changes
  • Better TTI: Prevents hydration failures which would cause worse performance

Precompile Hook:

  • The hook runs on every Shakapacker compilation
  • Early returns prevent unnecessary work when features aren't configured
  • ✅ Minimal performance impact in normal operation

🔒 Security Considerations

Strengths:

  1. ✅ Uses array form for system calls (lines 76, 80) - prevents shell injection
  2. ✅ Security comment in shakapacker.yml about trusted scripts
  3. ✅ Proper validation before executing commands

Best Practices Followed:

  • No use of string interpolation in system calls
  • Explicit path checking before file operations
  • Fail-fast behavior on errors

📦 Dependency Updates

The Gemfile.lock changes show:

  • Shakapacker: 8.0.0 → 9.3.0
  • semantic_range: 3.0.0 → 3.1.0
  • Open source adds: rbs gem

These dependency updates align with the PR's goals and are consistent with the project's CI configuration (as mentioned in CLAUDE.md).


📝 Documentation & Style

Strengths:

  • PR description is comprehensive with clear problem/solution sections
  • Code comments are helpful and explain the "why"
  • Follows repository conventions from CLAUDE.md

Minor Note:

  • Ensure CHANGELOG.md is updated (per CLAUDE.md guidelines) since this is a bug fix that affects users
  • Format: [PR 1977](https://github.com/shakacode/react_on_rails/pull/1977) by [justin808](https://github.com/justin808)

✨ Additional Recommendations

  1. DRY Opportunity: The precompile hook script is duplicated between open-source and Pro. Consider:

    • Symlinking to a shared script
    • Or documenting why duplication is intentional (if Pro needs to diverge in the future)
  2. Monitoring: Consider adding telemetry or logging to track:

    • How often the race condition would have occurred (if async were still used)
    • Hook execution time for performance monitoring
  3. Documentation Update: Update any documentation that mentions async: true as the recommended approach for Shakapacker >= 8.2.0


🎬 Conclusion

Overall Assessment: ✅ LGTM with minor suggestions

This is a solid fix that:

  • ✅ Addresses the root cause correctly
  • ✅ Follows project conventions
  • ✅ Maintains consistency across the monorepo
  • ✅ Includes proper error handling and security considerations
  • ✅ Has comprehensive documentation

The code quality is high and the approach is sound. The minor suggestions above are optional improvements that don't block merging.

Recommendation: Approve and merge after:

  1. CI passes (especially the Playwright E2E tests this targets)
  2. CHANGELOG.md is updated
  3. Consider the DRY opportunity for the precompile hook script

Great work on the thorough analysis and consistent implementation! 🚀

@claude
Copy link

claude bot commented Nov 11, 2025

Code Review - PR #1977

Summary

This PR successfully addresses Playwright E2E test timeouts in the Pro dummy app by aligning it with the defer loading strategy from PR #1972. The changes are well-motivated, properly documented, and follow established patterns from the open-source codebase.


Positive Aspects

1. Excellent Problem Analysis

The PR description clearly explains:

  • The specific symptom (30-second timeout waiting for console message)
  • The root cause (race condition between async script loading)
  • Why the fix works (defer ensures proper execution order)

2. Code Consistency

3. Security Awareness

The shakapacker.yml includes appropriate security documentation:

# SECURITY: Only reference trusted scripts within your project. Ensure the hook path
# points to a file within the project root that you control.

4. Robust Hook Implementation

The precompile hook demonstrates good practices:

  • Cross-platform compatibility (works with both yarn/npm)
  • Graceful degradation (returns early if dependencies not found)
  • Proper error handling with exit codes
  • Security-conscious (uses array form for system commands)
  • RuboCop compliant (disables only necessary complexity check with explanation)

Observations & Minor Suggestions

1. Comment Update in Layout ⚠️

The old comment in application.html.erb (lines 27-31 in current file) still references async: true and discusses Selective Hydration. While the new comment (lines 27-29 in the diff) is more accurate for the defer strategy, consider:

Before (lines 27-31):

<%# async: true is the recommended approach for Shakapacker >= 8.2.0 (currently using 9.3.0).
    It enables React 18's Selective Hydration and provides optimal Time to Interactive (TTI).
    Use immediate_hydration feature to control hydration timing for Selective/Immediate Hydration.
    See docs/building-features/streaming-server-rendering.md
    skip_js_packs param is used for testing purposes to simulate hydration failure %>

After (lines 27-29 in diff):

<%# Use defer: true to ensure proper script execution order.
    When using generated component packs (auto_load_bundle), defer ensures
    component registrations complete before React hydration begins.

Suggestion: The skip_js_packs usage context is lost in the new comment. Consider adding:

<%# Use defer: true to ensure proper script execution order.
    When using generated component packs (auto_load_bundle), defer ensures
    component registrations complete before React hydration begins.
    skip_js_packs param is used for testing purposes to simulate hydration failure %>

2. Gemfile.lock Changes ℹ️

The PR includes Shakapacker version bump from 8.0.0 → 9.3.0 and semantic_range updates. These appear intentional for aligning with the "Latest" CI configuration, but worth confirming:

  • Is this a deliberate upgrade as part of CI alignment?
  • Have all Shakapacker 9.3.0 compatibility concerns been addressed?

3. Test Verification 📋

The test plan checklist is still unchecked. Ensure:

  • Playwright E2E tests pass in CI
  • ToggleContainer console message appears (line 52 in streaming.spec.ts)
  • All 11 streaming tests complete without timeouts

4. rbs Gem Addition ℹ️

The open-source Gemfile.lock shows rbs was added as a dependency. This seems unrelated to the defer loading fix - was this intentional or a side effect of bundle operations?


Code Quality Assessment

Security ✅

  • Hook script uses secure command execution patterns (system with array form)
  • Proper path validation for Rails root detection
  • Security warnings documented in config

Performance ✅

  • Defer loading maintains parallel download benefits while fixing execution order
  • Hook runs only when needed (checks for config presence)
  • Early returns prevent unnecessary work

Test Coverage ✅

  • Existing Playwright tests (streaming.spec.ts:52) will verify the fix
  • Tests specifically check for the console message that was timing out

Best Practices ✅

  • Follows repository conventions per CLAUDE.md
  • Ruby script has proper shebang and frozen_string_literal
  • Cross-platform compatibility considered
  • Error handling with meaningful messages

Potential Issues

None Identified 🎉

No bugs, security vulnerabilities, or significant issues found. The implementation is solid.


Recommendations

  1. Verify CI Passes: Ensure all Playwright tests complete successfully
  2. Update Comment: Consider preserving the skip_js_packs documentation in the ERB comment
  3. Confirm Shakapacker Upgrade: Verify the 8.0.0 → 9.3.0 upgrade is intentional and complete
  4. Check Test Plan: Mark off the checklist items in the PR description once verified

Conclusion

Recommendation: Approve with minor suggestions

This is a well-executed fix that:

The minor suggestions above are non-blocking improvements. Great work tracking down the race condition and applying the consistent fix across both codebases!


Review conducted using repository guidelines from CLAUDE.md

@justin808 justin808 force-pushed the justin808/playwright-streaming-timeout branch from c8be1f5 to 36d768b Compare November 12, 2025 21:31
…ding strategy

Apply the same defer loading strategy fix from commit d1a8a1a to the Pro dummy app
to resolve race condition causing Playwright test timeouts.

## Problem
Playwright E2E tests for streaming were timing out waiting for console message
"ToggleContainer with title", indicating React components weren't hydrating.

## Root Cause
The Pro dummy app was still using async: true for javascript_pack_tag while the
open-source dummy app was updated to defer: true in commit d1a8a1a. This created
a race condition where:
- Generated component packs load asynchronously
- Main client-bundle also loads asynchronously
- If client-bundle executes before component registrations complete, React tries
  to hydrate unregistered components
- ToggleContainer never hydrates, useEffect never runs, console.log never fires

## Solution
1. Changed javascript_pack_tag from async: true to defer: true in application.html.erb
2. Added precompile_hook to shakapacker.yml for pack generation
3. Added bin/shakapacker-precompile-hook script

Using defer: true ensures script execution order - generated component packs load
and register components before main bundle executes, preventing the race condition.

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

Co-Authored-By: Claude <noreply@anthropic.com>
@justin808 justin808 force-pushed the justin808/playwright-streaming-timeout branch from 36d768b to cbb7df2 Compare November 13, 2025 03:23
@claude
Copy link

claude bot commented Nov 13, 2025

Code Review: PR #1977

Overview

This PR fixes Playwright E2E test timeouts in the Pro dummy app by applying the same defer loading strategy from #1972. The changes are well-motivated and align the Pro dummy app with the open-source configuration.


✅ Strengths

  1. Root Cause Analysis: Excellent problem diagnosis. The PR clearly identifies the race condition where async: true caused unpredictable script execution order, leading to React hydration failures.

  2. Consistent Solution: Mirrors the fix from Fix component registration race condition with defer loading strategy #1972 (commit d1a8a1a), maintaining consistency across the monorepo.

  3. Comprehensive Fix: Addresses both the immediate issue (defer loading) and the underlying build automation (precompile hook).

  4. Good Documentation: Clear comments in the ERB template explaining why defer: true is needed for auto-generated component packs.

  5. Executable Permissions: The precompile hook has correct permissions (755).

  6. Cross-platform Compatibility: The precompile hook script handles multiple scenarios (yarn/npm, bundle availability) gracefully.


🔍 Issues & Recommendations

1. Security: Command Injection Risk (Medium Priority)

Location: bin/shakapacker-precompile-hook:35-37

success = if yarn_available
            system("yarn", "build:rescript")
          elsif npm_available
            system("npm", "run", "build:rescript")

Issue: The script uses the array form of system() which is secure, but this should be explicitly documented as a security best practice since this hook runs during build/deployment.

Recommendation: Add a comment explaining the security consideration:

# Use array form to prevent shell injection
success = if yarn_available
            system("yarn", "build:rescript")

2. Error Handling: Silent Failures (Low Priority)

Location: bin/shakapacker-precompile-hook:39-40

else
  warn "⚠️  Warning: Neither yarn nor npm found. Skipping ReScript build."
  return

Issue: When neither package manager is found, the script warns but continues. If ReScript is required, this could lead to broken builds that fail later.

Recommendation: Consider making this configurable or failing fast if ReScript config exists:

else
  warn "❌ Error: ReScript config found but no package manager available"
  exit 1
end

3. Code Quality: Complex Regex (Low Priority)

Location: bin/shakapacker-precompile-hook:65-66

has_auto_load = config_file =~ /^\s*(?\!#).*config\.auto_load_bundle\s*=/
has_components_subdir = config_file =~ /^\s*(?\!#).*config\.components_subdirectory\s*=/

Issue: The negative lookahead (?\!#) doesn't correctly skip comments. A line like # config.auto_load_bundle = true would match because the lookahead only checks the first character after whitespace.

Recommendation: Use a more robust regex:

# Match lines that don't start with # (after optional whitespace)
has_auto_load = config_file =~ /^\s*[^#]*config\.auto_load_bundle\s*=/
has_components_subdir = config_file =~ /^\s*[^#]*config\.components_subdirectory\s*=/

Or use Ruby parsing:

has_auto_load = config_file.lines.any? { |line| line.strip[0] \!= '#' && line.include?('config.auto_load_bundle =') }

4. Missing Test Coverage (Medium Priority)

Issue: No tests for the precompile hook script. Given its critical role in the build process, it should have unit tests.

Recommendation: Add tests in react_on_rails_pro/spec/dummy/spec/bin/shakapacker_precompile_hook_spec.rb:

  • Test ReScript build detection
  • Test Rails root finding
  • Test config parsing (especially edge cases with comments)
  • Test error handling

5. Documentation: Shakapacker Version (Low Priority)

Location: config/shakapacker.yml:25

The comment references Shakapacker docs, but doesn't specify the minimum version required for precompile_hook support.

Recommendation: Add version requirement to the comment:

# Hook to run before webpack compilation (requires Shakapacker >= X.X.X)

🧪 Testing Considerations

The PR description mentions a test plan but doesn't show completed checkboxes. Before merging:

  • ✅ Verify CI passes (especially the Playwright streaming tests)
  • ✅ Confirm ToggleContainer console message appears (check CI logs)
  • ✅ Test the precompile hook runs correctly in both development and production builds
  • ✅ Verify the hook doesn't break builds when ReScript isn't present

🎯 Performance Considerations

Positive: Using defer: true maintains parallel download performance while ensuring deterministic execution order. This is the optimal strategy for this use case.

Neutral: The precompile hook adds build time overhead, but this is necessary for auto-generated packs.


📋 Summary

Approve with minor suggestions. The core fix is sound and well-implemented. The issues identified are mostly improvements rather than blockers:

  • Must fix: None (code is mergeable)
  • Should fix: Add test coverage for the precompile hook
  • Nice to have: Improve regex robustness, add security comments

The PR successfully addresses the test timeout issue and aligns the Pro dummy app with the open-source configuration. Great work documenting the root cause and solution!


📚 References

- Remove unnecessary rubocop disable/enable directives
- Fix Style/SymbolArray violation in shakapacker-precompile-hook
- Add explanatory comment about generated_component_packs_loading_strategy
  defaulting to :defer to match OSS dummy app configuration

Note: The failing "React Router Sixth Page" RSpec test is a known flaky
test that also fails intermittently on master. This is not a regression
from the defer loading strategy changes.

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

Co-Authored-By: Claude <noreply@anthropic.com>
@claude
Copy link

claude bot commented Nov 13, 2025

Code Review for PR #1977

Thank you for this PR! The changes effectively address the Playwright test timeout issue by aligning the Pro dummy app with the open-source configuration. Here's my detailed review:


Strengths

1. Root Cause Analysis is Solid

The PR correctly identifies the race condition caused by async: true loading strategy where component registration might not complete before React hydration begins. The switch to defer: true is the right solution.

2. Consistency Across Codebase

Aligning the Pro dummy app with the open-source dummy app (from #1972) ensures consistent behavior and reduces maintenance burden.

3. Comprehensive Solution

The PR includes all necessary pieces:

  • Changed loading strategy in application.html.erb
  • Added precompile hook for automatic pack generation
  • Updated configuration files
  • Added helpful comments explaining the rationale

4. Good Documentation

The inline comments explain why defer: true is used, which is crucial for future maintainers.


🔍 Observations & Suggestions

1. Code Duplication: Precompile Hook

The shakapacker-precompile-hook file in the Pro dummy app is nearly identical to the one in the open-source dummy app, with one difference:

Pro version (line 75):

task_list = IO.popen(["bundle", "exec", "rails", "-T"], err: %i[child out], &:read)

Open-source version (line 76):

task_list = IO.popen(["bundle", "exec", "rails", "-T"], err: [:child, :out], &:read)

The Pro version uses %i[child out] (symbol array literal) while the open-source version uses [:child, :out] (explicit array). Both are functionally equivalent.

Suggestion: Consider one of these approaches to reduce duplication:

  • Extract the hook to a shared location (e.g., lib/generators/react_on_rails/templates/)
  • Add a note in both files indicating they should be kept in sync
  • Use a symlink if the codebase structure supports it

2. RuboCop Violation in Pro Version

The open-source version has a RuboCop disable comment for Metrics/CyclomaticComplexity (lines 52, 89), but the Pro version doesn't. This might cause CI failures if RuboCop runs on the Pro package.

Action Required:

# Add these lines to react_on_rails_pro/spec/dummy/bin/shakapacker-precompile-hook
# rubocop:disable Metrics/CyclomaticComplexity
def generate_packs_if_needed
  # ... existing code ...
end
# rubocop:enable Metrics/CyclomaticComplexity

3. Security Note is Well-Placed

The security comment in shakapacker.yml about trusting the precompile hook script is excellent. This is important for preventing arbitrary code execution.

4. Test Coverage

The PR description mentions test timeouts were the issue, and the fix ensures proper hydration. However:

  • Question: Have you verified that all 11 streaming tests now pass consistently?
  • Suggestion: Consider adding a test that explicitly verifies the script loading order (though this might be overkill for this issue)

5. Comment Accuracy in react_on_rails.rb

Lines 42-43 in react_on_rails_pro/spec/dummy/config/initializers/react_on_rails.rb:

# Don't explicitly set generated_component_packs_loading_strategy - let it default to :defer
# which ensures generated component packs load and register components before main bundle executes

This is good documentation. However, it might be worth explicitly stating that this default behavior changed or was introduced in a specific version for future reference.


🐛 Potential Issues

1. Missing RuboCop Pragmas (Critical)

As mentioned above, the Pro version is missing the RuboCop disable comment that exists in the open-source version. This will cause CI failures based on the CLAUDE.md requirements:

BEFORE EVERY COMMIT/PUSH:

  1. ALWAYS run bundle exec rubocop and fix ALL violations

Priority: High - Should be fixed before merging.

2. File Permissions

The script needs to be executable. Based on the diff, the file is new. Ensure it has execute permissions:

chmod +x react_on_rails_pro/spec/dummy/bin/shakapacker-precompile-hook

Git should preserve this, but worth verifying.


🎯 Performance Considerations

Script Loading Performance

Before (async): Scripts load in parallel, potentially faster initial load but risk of race conditions.
After (defer): Scripts load in parallel but execute in order, slightly slower but reliable.

Trade-off is acceptable because:

  • The performance difference is negligible (milliseconds)
  • Correctness > speed in this case
  • User experience improves due to proper hydration

Precompile Hook Performance

The hook runs during precompilation, not runtime, so performance impact is minimal. The conditional checks (file existence, config parsing) are efficient.


🔒 Security Considerations

Precompile Hook Execution

Good: The hook uses array form for system calls (system("bundle", "exec", "rails", ...)), which prevents shell injection.
Good: Security warning in shakapacker.yml alerts users to only use trusted scripts.
Good: Hook validates command availability before execution.

No security concerns identified.


📋 Required Actions Before Merge

  1. Add RuboCop pragmas to react_on_rails_pro/spec/dummy/bin/shakapacker-precompile-hook (lines 52 and 87)
  2. Run bundle exec rubocop on the Pro package to verify no violations
  3. Verify all Playwright tests pass in CI
  4. Confirm file permissions are correct for the hook script

📝 Optional Improvements

  1. Consider documenting the code duplication between Pro and open-source hook scripts
  2. Add a test that verifies the precompile hook runs successfully during asset compilation
  3. Consider adding a comment in the open-source hook pointing to the Pro version and vice versa

Overall Assessment

Rating: Approve with minor changes

This is a well-researched fix that addresses a real issue with a proven solution. The root cause analysis is thorough, and the implementation is sound. The only blocking issue is the missing RuboCop pragma, which should be a quick fix.

Recommended next steps:

  1. Add the RuboCop disable/enable comments
  2. Run bundle exec rubocop to verify
  3. Confirm CI passes
  4. Merge

Great work on identifying and fixing this race condition! 🚀

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 36d768b and 9c9abd1.

📒 Files selected for processing (4)
  • react_on_rails_pro/spec/dummy/app/views/layouts/application.html.erb (1 hunks)
  • react_on_rails_pro/spec/dummy/bin/shakapacker-precompile-hook (1 hunks)
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails.rb (1 hunks)
  • react_on_rails_pro/spec/dummy/config/shakapacker.yml (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails.rb
🚧 Files skipped from review as they are similar to previous changes (2)
  • react_on_rails_pro/spec/dummy/app/views/layouts/application.html.erb
  • react_on_rails_pro/spec/dummy/config/shakapacker.yml
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: justin808
Repo: shakacode/react_on_rails PR: 1770
File: lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx:2-2
Timestamp: 2025-09-16T08:01:11.146Z
Learning: React on Rails uses webpack CSS Modules configuration with namedExports: true, which requires the import syntax `import * as style from './file.module.css'` rather than the default export pattern. This configuration enables better tree shaking and bundle size optimization for CSS modules.
Learnt from: alexeyr-ci2
Repo: shakacode/react_on_rails PR: 1732
File: spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx:40-44
Timestamp: 2025-04-26T21:55:55.874Z
Learning: In the react_on_rails project, files under `app-react16` directories are copied/moved to corresponding `/app` directories during the conversion process (removing the `-react16` suffix), which affects their relative import paths at runtime.
Learnt from: theforestvn88
Repo: shakacode/react_on_rails PR: 1620
File: spec/dummy/client/app/startup/HelloTurboStream.jsx:3-3
Timestamp: 2024-10-08T20:53:47.076Z
Learning: The `RailsContext` import in `spec/dummy/client/app/startup/HelloTurboStream.jsx` is used later in the project, as clarified by the user theforestvn88.
Learnt from: theforestvn88
Repo: shakacode/react_on_rails PR: 1620
File: spec/dummy/client/app/startup/HelloTurboStream.jsx:3-3
Timestamp: 2024-07-27T10:08:35.868Z
Learning: The `RailsContext` import in `spec/dummy/client/app/startup/HelloTurboStream.jsx` is used later in the project, as clarified by the user theforestvn88.
Learnt from: Romex91
Repo: shakacode/react_on_rails PR: 1697
File: package-scripts.yml:28-28
Timestamp: 2025-02-12T16:38:06.537Z
Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.
📚 Learning: 2025-02-12T16:38:06.537Z
Learnt from: Romex91
Repo: shakacode/react_on_rails PR: 1697
File: package-scripts.yml:28-28
Timestamp: 2025-02-12T16:38:06.537Z
Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.

Applied to files:

  • react_on_rails_pro/spec/dummy/bin/shakapacker-precompile-hook
📚 Learning: 2025-04-26T21:55:55.874Z
Learnt from: alexeyr-ci2
Repo: shakacode/react_on_rails PR: 1732
File: spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx:40-44
Timestamp: 2025-04-26T21:55:55.874Z
Learning: In the react_on_rails project, files under `app-react16` directories are copied/moved to corresponding `/app` directories during the conversion process (removing the `-react16` suffix), which affects their relative import paths at runtime.

Applied to files:

  • react_on_rails_pro/spec/dummy/bin/shakapacker-precompile-hook
📚 Learning: 2025-07-08T05:57:29.630Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1745
File: node_package/src/RSCRequestTracker.ts:8-14
Timestamp: 2025-07-08T05:57:29.630Z
Learning: The global `generateRSCPayload` function in React on Rails Pro (RORP) is provided by the framework during rendering requests, not implemented in application code. The `declare global` statements are used to document the expected interface that RORP will inject at runtime.

Applied to files:

  • react_on_rails_pro/spec/dummy/bin/shakapacker-precompile-hook
📚 Learning: 2025-09-16T08:01:11.146Z
Learnt from: justin808
Repo: shakacode/react_on_rails PR: 1770
File: lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx:2-2
Timestamp: 2025-09-16T08:01:11.146Z
Learning: React on Rails uses webpack CSS Modules configuration with namedExports: true, which requires the import syntax `import * as style from './file.module.css'` rather than the default export pattern. This configuration enables better tree shaking and bundle size optimization for CSS modules.

Applied to files:

  • react_on_rails_pro/spec/dummy/bin/shakapacker-precompile-hook
📚 Learning: 2025-10-23T17:22:01.074Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1875
File: lib/react_on_rails/utils.rb:112-124
Timestamp: 2025-10-23T17:22:01.074Z
Learning: In React on Rails, when Pro is installed but not licensed, the intended behavior is to raise an error on boot. The `react_on_rails_pro?` method validates licenses and should raise errors early (including during path resolution in methods like `server_bundle?`) to enforce licensing requirements rather than failing later with obscure errors.

Applied to files:

  • react_on_rails_pro/spec/dummy/bin/shakapacker-precompile-hook
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: lint-js-and-ruby
  • GitHub Check: claude-review

Comment on lines +24 to +49
def build_rescript_if_needed
# Check for both old (bsconfig.json) and new (rescript.json) config files
return unless File.exist?("bsconfig.json") || File.exist?("rescript.json")

puts "🔧 Building ReScript..."

# Cross-platform package manager detection
yarn_available = system("yarn", "--version", out: File::NULL, err: File::NULL)
npm_available = system("npm", "--version", out: File::NULL, err: File::NULL)

success = if yarn_available
system("yarn", "build:rescript")
elsif npm_available
system("npm", "run", "build:rescript")
else
warn "⚠️ Warning: Neither yarn nor npm found. Skipping ReScript build."
return
end

if success
puts "✅ ReScript build completed successfully"
else
warn "❌ ReScript build failed"
exit 1
end
end
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use the Rails root when detecting and building ReScript assets.

build_rescript_if_needed works off the current working directory. When the precompile hook is invoked from outside the Rails root (e.g., when Shakapacker spawns the hook from the packs output directory), the File.exist? checks will miss bsconfig.json/rescript.json, and the subsequent yarn build:rescript will run without the right package.json. The build is then skipped or fails silently, so generated ReScript packs never reach the Playwright run.

Please resolve this by anchoring both the existence checks and the build command to rails_root (and aborting early if it can’t be found). For example:

 def build_rescript_if_needed
-  return unless File.exist?("bsconfig.json") || File.exist?("rescript.json")
+  rails_root = find_rails_root
+  return unless rails_root
+
+  config_files = %w[bsconfig.json rescript.json].map { |name| File.join(rails_root, name) }
+  return unless config_files.any? { |path| File.exist?(path) }
 
   puts "🔧 Building ReScript..."
 
-  yarn_available = system("yarn", "--version", out: File::NULL, err: File::NULL)
-  npm_available = system("npm", "--version", out: File::NULL, err: File::NULL)
+  yarn_available = system("yarn", "--version", out: File::NULL, err: File::NULL)
+  npm_available = system("npm", "--version", out: File::NULL, err: File::NULL)
 
-  success = if yarn_available
-              system("yarn", "build:rescript")
-            elsif npm_available
-              system("npm", "run", "build:rescript")
-            else
-              warn "⚠️  Warning: Neither yarn nor npm found. Skipping ReScript build."
-              return
-            end
+  success = Dir.chdir(rails_root) do
+    if yarn_available
+      system("yarn", "build:rescript")
+    elsif npm_available
+      system("npm", "run", "build:rescript")
+    else
+      warn "⚠️  Warning: Neither yarn nor npm found. Skipping ReScript build."
+      return
+    end
+  end

This guarantees we always evaluate the right config files and run the package-manager script where package.json lives.

🤖 Prompt for AI Agents
In react_on_rails_pro/spec/dummy/bin/shakapacker-precompile-hook around lines 24
to 49, the ReScript detection and build run against the current working
directory which can be outside the Rails root; change the logic to first resolve
rails_root (abort early if missing), then use File.join(rails_root,
"bsconfig.json") and File.join(rails_root, "rescript.json") for existence
checks, and run the package manager commands from rails_root (e.g., invoke
system within that directory or chdir temporarily) so yarn/npm sees the correct
package.json; ensure the function returns/errs appropriately if rails_root is
not found or the build command fails.

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.

2 participants