react_on_rails 17.0.0.rc.2 → 17.0.0.rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/lib/generators/USAGE +6 -0
  4. data/lib/generators/react_on_rails/base_generator.rb +20 -2
  5. data/lib/generators/react_on_rails/generator_helper.rb +7 -0
  6. data/lib/generators/react_on_rails/install_generator.rb +58 -1
  7. data/lib/generators/react_on_rails/js_dependency_manager.rb +36 -0
  8. data/lib/generators/react_on_rails/react_no_redux_generator.rb +20 -8
  9. data/lib/generators/react_on_rails/react_with_redux_generator.rb +28 -4
  10. data/lib/generators/react_on_rails/rsc_generator.rb +5 -0
  11. data/lib/generators/react_on_rails/rsc_setup.rb +35 -1
  12. data/lib/generators/react_on_rails/templates/agent_files/.cursor/rules/react-on-rails.mdc +11 -0
  13. data/lib/generators/react_on_rails/templates/agent_files/.github/copilot-instructions.md +7 -0
  14. data/lib/generators/react_on_rails/templates/agent_files/AGENTS.md +164 -0
  15. data/lib/generators/react_on_rails/templates/agent_files/CLAUDE.md +8 -0
  16. data/lib/generators/react_on_rails/templates/base/base/config/webpack/commonWebpackConfig.js.tt +81 -1
  17. data/lib/generators/react_on_rails/templates/base/tailwind/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx +30 -0
  18. data/lib/generators/react_on_rails/templates/base/tailwind/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.tsx +34 -0
  19. data/lib/generators/react_on_rails/templates/base/tailwind/app/javascript/stylesheets/application.css +1 -0
  20. data/lib/generators/react_on_rails/templates/redux/tailwind/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +25 -0
  21. data/lib/generators/react_on_rails/templates/redux/tailwind/app/javascript/bundles/HelloWorld/components/HelloWorld.tsx +29 -0
  22. data/lib/react_on_rails/doctor.rb +187 -24
  23. data/lib/react_on_rails/font_helper.rb +162 -0
  24. data/lib/react_on_rails/helper.rb +150 -0
  25. data/lib/react_on_rails/smart_error.rb +135 -5
  26. data/lib/react_on_rails/version.rb +1 -1
  27. data/lib/tasks/doctor.rake +5 -2
  28. data/sig/react_on_rails/helper.rbs +3 -0
  29. data/sig/react_on_rails/smart_error.rbs +25 -2
  30. metadata +11 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5610a42464b29a2f8066e2a91d342838cdd1f3734c3965864e0a5bd0d18e4c8a
4
- data.tar.gz: 551fc429af6eea0798b929d0aece8b417b4646dfed702c1dcd39ae26d52084ad
3
+ metadata.gz: 0630c2730fc7389d31e1f99d88e8df8dc6eb18b670e2909afd25e1e71b0c50ab
4
+ data.tar.gz: 6db462a802556e23085ae038585dd7c310c7710fd41a0a336d350383e85b9333
5
5
  SHA512:
6
- metadata.gz: 4b1df35fc94bc088e6a9afe96ca54ec651951149ae82415e55d1db023ef582089fb58f9536db698d0971633678b3d2d70188c0ca355217fd9f6a854ead1f177c
7
- data.tar.gz: c1371c43729036f9f9dbb78d68b85be81919a98a16fb8d3d4185b94c0f48c92506404bedb6e1a7a804b52596ae784b4f60487cc4bad8333f0933e38aa005551a
6
+ metadata.gz: 395e61f25e352b69df2788554dd0fcba85834df3e2f42bd768b6c2a81c8579e2d2504da1a47417abcc8ee5369181260c54f828e206f36dba409cbeef5a5818a9
7
+ data.tar.gz: 5ee8406c0ce296a4dca6e38299c1274fd2e5b6dde7f62f83f9790122dfc44cf88c0ca2cc093a9da7089265e53ed08ded28a018efc1929828c3a3ced000ba41c4
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- react_on_rails (17.0.0.rc.2)
4
+ react_on_rails (17.0.0.rc.3)
5
5
  addressable
6
6
  connection_pool
7
7
  execjs (~> 2.5)
data/lib/generators/USAGE CHANGED
@@ -8,6 +8,12 @@ The react_on_rails:install generator integrates a React frontend, including SSR,
8
8
  to integrate the Redux state container framework. The necessary node modules
9
9
  will be automatically included for you.
10
10
 
11
+ * Tailwind CSS v4 (Optional)
12
+
13
+ Passing the --tailwind generator option installs Tailwind CSS v4, configures
14
+ the PostCSS plugin for Webpack or Rspack, and styles the generated
15
+ server-rendered Hello World example.
16
+
11
17
  *******************************************************************************
12
18
 
13
19
  After running the generator, you will want to:
@@ -41,6 +41,12 @@ module ReactOnRails
41
41
  type: :boolean,
42
42
  desc: "Use Webpack as the bundler (alias for --no-rspack; --no-webpack is equivalent to --rspack)"
43
43
 
44
+ # --tailwind
45
+ class_option :tailwind,
46
+ type: :boolean,
47
+ default: false,
48
+ desc: "Install Tailwind CSS v4 and style the generated SSR example"
49
+
44
50
  # --pro
45
51
  class_option :pro,
46
52
  type: :boolean,
@@ -106,6 +112,10 @@ module ReactOnRails
106
112
  generator.__send__(:use_rsc?)
107
113
  end
108
114
 
115
+ def use_tailwind?
116
+ generator.__send__(:use_tailwind?)
117
+ end
118
+
109
119
  def shakapacker_version_9_or_higher?
110
120
  generator.__send__(:shakapacker_version_9_or_higher?)
111
121
  end
@@ -228,7 +238,7 @@ module ReactOnRails
228
238
  base_files = %w[app/javascript/packs/server-bundle.js]
229
239
 
230
240
  # Skip HelloWorld CSS for Redux (uses HelloWorldApp) or RSC (uses HelloServer)
231
- unless options.redux? || use_rsc?
241
+ unless options.redux? || use_rsc? || use_tailwind?
232
242
  base_files << "app/javascript/src/HelloWorld/ror_components/HelloWorld.module.css"
233
243
  end
234
244
 
@@ -259,6 +269,14 @@ module ReactOnRails
259
269
  copy_webpack_main_config(base_path, config)
260
270
  end
261
271
 
272
+ def copy_tailwind_files
273
+ return unless use_tailwind?
274
+
275
+ base_path = "base/tailwind/"
276
+ copy_file("#{base_path}app/javascript/stylesheets/application.css",
277
+ "app/javascript/stylesheets/application.css")
278
+ end
279
+
262
280
  def copy_packer_config
263
281
  base_path = "base/base/"
264
282
  config = "config/shakapacker.yml"
@@ -970,7 +988,7 @@ module ReactOnRails
970
988
  # Note: files originally generated with --pro or --rsc will not match when the
971
989
  # current run omits those options; in that case, we preserve the directory.
972
990
  # Templates rely on config[:message] plus a small helper subset exposed by
973
- # TemplateRenderContext (add_documentation_reference, use_pro?, use_rsc?,
991
+ # TemplateRenderContext (add_documentation_reference, use_pro?, use_rsc?, use_tailwind?,
974
992
  # shakapacker_version_9_or_higher?, rsc_plugin_class_name, rsc_plugin_import_path).
975
993
  # Missing method delegates raise NoMethodError and are caught below, treating the
976
994
  # file as non-removable.
@@ -121,6 +121,13 @@ module GeneratorHelper
121
121
  options[:rsc]
122
122
  end
123
123
 
124
+ # Check if Tailwind CSS should be installed and wired into the generated example.
125
+ #
126
+ # @return [Boolean] true if --tailwind is set
127
+ def use_tailwind?
128
+ options[:tailwind]
129
+ end
130
+
124
131
  # Determine if the project is using rspack as the bundler.
125
132
  #
126
133
  # Detection priority:
@@ -43,6 +43,12 @@ module ReactOnRails
43
43
  desc: "Generate TypeScript files and install TypeScript dependencies. Default: false",
44
44
  aliases: "-T"
45
45
 
46
+ # --tailwind
47
+ class_option :tailwind,
48
+ type: :boolean,
49
+ default: false,
50
+ desc: "Install Tailwind CSS v4 and style the generated SSR example. Default: false"
51
+
46
52
  # --rspack / --no-rspack (Rspack is the default on fresh installs; --no-rspack selects Webpack)
47
53
  # IMPORTANT: do NOT add a `default:` here. The absence of a default is load-bearing — Thor
48
54
  # only includes :rspack in the options hash when the flag is explicitly passed, which is how
@@ -66,6 +72,15 @@ module ReactOnRails
66
72
  default: false,
67
73
  desc: "Skip warnings. Default: false"
68
74
 
75
+ # --agent-files / --no-agent-files
76
+ # Emits consumer-scoped AI-agent guidance (AGENTS.md) plus thin editor pointer
77
+ # files (CLAUDE.md, .cursor/rules/react-on-rails.mdc, .github/copilot-instructions.md).
78
+ # Default ON; pass --no-agent-files to skip. Existing files are never overwritten.
79
+ class_option :agent_files,
80
+ type: :boolean,
81
+ default: true,
82
+ desc: "Write AI-agent guidance files (AGENTS.md + editor pointers). Default: true"
83
+
69
84
  # --pro
70
85
  class_option :pro,
71
86
  type: :boolean,
@@ -183,6 +198,7 @@ module ReactOnRails
183
198
  add_package_json_scripts
184
199
  add_ci_workflow
185
200
  add_bin_scripts
201
+ add_agent_files
186
202
  add_post_install_message
187
203
  else
188
204
  error = <<~MSG.strip
@@ -227,7 +243,7 @@ module ReactOnRails
227
243
  # --pretend/--force/--skip must be forwarded explicitly at each boundary.
228
244
  invoke "react_on_rails:base", [],
229
245
  { typescript: options.typescript?, redux: options.redux?, rspack: using_rspack?,
230
- pro: use_pro?, rsc: use_rsc?, new_app: options.new_app?,
246
+ pro: use_pro?, rsc: use_rsc?, tailwind: use_tailwind?, new_app: options.new_app?,
231
247
  shakapacker_just_installed: shakapacker_just_installed?,
232
248
  force: options[:force], skip: options[:skip], pretend: options[:pretend] }
233
249
 
@@ -237,6 +253,7 @@ module ReactOnRails
237
253
  # - Without --rsc: Normal behavior (HelloWorld or HelloWorldApp based on --redux)
238
254
  if options.redux?
239
255
  invoke "react_on_rails:react_with_redux", [], { typescript: options.typescript?,
256
+ tailwind: use_tailwind?,
240
257
  invoked_by_install: true,
241
258
  new_app: options.new_app?,
242
259
  rsc: use_rsc?,
@@ -246,6 +263,7 @@ module ReactOnRails
246
263
  # Only generate HelloWorld if RSC is not enabled
247
264
  # For RSC, HelloServer replaces HelloWorld as the example component
248
265
  invoke "react_on_rails:react_no_redux", [], { typescript: options.typescript?,
266
+ tailwind: use_tailwind?,
249
267
  new_app: options.new_app?,
250
268
  force: options[:force], skip: options[:skip],
251
269
  pretend: options[:pretend] }
@@ -265,6 +283,7 @@ module ReactOnRails
265
283
 
266
284
  invoke "react_on_rails:rsc", [], { typescript: options.typescript?, invoked_by_install: true,
267
285
  new_app: options.new_app?, redux: options.redux?,
286
+ tailwind: use_tailwind?,
268
287
  force: options[:force], skip: options[:skip],
269
288
  pretend: options[:pretend] }
270
289
  end
@@ -622,6 +641,40 @@ module ReactOnRails
622
641
  File.chmod(0o755, *files_to_become_executable)
623
642
  end
624
643
 
644
+ # Consumer-scoped AI-agent guidance written into the generated app. The canonical
645
+ # AGENTS.md content lives in templates/agent_files/ and is the single source of truth;
646
+ # create-react-on-rails-app gets it for free because it delegates to this generator.
647
+ # Each file is copied only when absent so we never clobber an app's existing agent files.
648
+ AGENT_FILES = %w[
649
+ AGENTS.md
650
+ CLAUDE.md
651
+ .cursor/rules/react-on-rails.mdc
652
+ .github/copilot-instructions.md
653
+ ].freeze
654
+ private_constant :AGENT_FILES
655
+
656
+ def add_agent_files
657
+ return unless options.agent_files?
658
+
659
+ # AGENTS.md is the canonical file the editor pointers (CLAUDE.md, Cursor, Copilot) all
660
+ # reference. If the app already has its own AGENTS.md, it may document unrelated
661
+ # conventions, so leave it untouched AND skip the pointer files rather than emit editor
662
+ # guidance pointing at an AGENTS.md we did not write.
663
+ if File.exist?(File.join(destination_root, "AGENTS.md"))
664
+ say_status :skip, "AGENTS.md already exists; leaving it and the editor pointer files untouched", :yellow
665
+ return
666
+ end
667
+
668
+ AGENT_FILES.each do |relative_path|
669
+ if File.exist?(File.join(destination_root, relative_path))
670
+ say_status :skip, "#{relative_path} already exists; leaving it untouched", :yellow
671
+ next
672
+ end
673
+
674
+ copy_file("templates/agent_files/#{relative_path}", relative_path)
675
+ end
676
+ end
677
+
625
678
  def replace_stock_rails_bin_dev!
626
679
  @preserve_existing_bin_dev = false
627
680
 
@@ -722,6 +775,10 @@ module ReactOnRails
722
775
  flags << "--pro"
723
776
  end
724
777
 
778
+ # Preserve an explicit agent-files opt-out so the suggested re-run doesn't emit
779
+ # AGENTS.md/editor files a user deliberately skipped (--agent-files defaults to on).
780
+ flags << "--no-agent-files" unless options.agent_files?
781
+
725
782
  ["rails generate react_on_rails:install", *flags].join(" ")
726
783
  end
727
784
 
@@ -83,6 +83,15 @@ module ReactOnRails
83
83
  style-loader@^4.0.0
84
84
  ].freeze
85
85
 
86
+ # Tailwind v4 CSS-first setup. The patch-level floors match published
87
+ # releases verified with the generator's SSR smoke app.
88
+ TAILWIND_DEPENDENCIES = %w[
89
+ tailwindcss@^4.3.0
90
+ @tailwindcss/postcss@^4.3.0
91
+ postcss@^8.5.15
92
+ postcss-loader@^8.2.1
93
+ ].freeze
94
+
86
95
  # Development-only dependencies for hot reloading (Webpack)
87
96
  # Both packages are pre-1.0, so left bare (see pinning note above).
88
97
  DEV_DEPENDENCIES = %w[
@@ -175,6 +184,7 @@ module ReactOnRails
175
184
  add_react_on_rails_package unless using_pro
176
185
  add_react_dependencies
177
186
  add_css_dependencies
187
+ add_tailwind_dependencies_if_requested
178
188
  add_rspack_dependencies if using_rspack?
179
189
  add_transpiler_dependencies
180
190
  add_pro_dependencies if using_pro
@@ -290,6 +300,32 @@ module ReactOnRails
290
300
  MSG
291
301
  end
292
302
 
303
+ def add_tailwind_dependencies
304
+ say "Installing Tailwind CSS v4 dependencies..."
305
+ return if add_packages(TAILWIND_DEPENDENCIES)
306
+
307
+ GeneratorMessages.add_warning(<<~MSG.strip)
308
+ ⚠️ Failed to add Tailwind CSS dependencies.
309
+
310
+ You can install them manually by running:
311
+ #{manual_add_packages_command(TAILWIND_DEPENDENCIES)}
312
+ MSG
313
+ rescue StandardError => e
314
+ GeneratorMessages.add_warning(<<~MSG.strip)
315
+ ⚠️ Error adding Tailwind CSS dependencies: #{e.message}
316
+
317
+ You can install them manually by running:
318
+ #{manual_add_packages_command(TAILWIND_DEPENDENCIES)}
319
+ MSG
320
+ end
321
+
322
+ def add_tailwind_dependencies_if_requested
323
+ # use_tailwind? is provided by GeneratorHelper, included alongside this module.
324
+ return unless use_tailwind?
325
+
326
+ add_tailwind_dependencies
327
+ end
328
+
293
329
  def add_rspack_dependencies
294
330
  say "Installing Rspack core dependencies..."
295
331
  return if add_packages(RSPACK_DEPENDENCIES)
@@ -18,6 +18,11 @@ module ReactOnRails
18
18
  default: false,
19
19
  desc: "Generate TypeScript files"
20
20
 
21
+ class_option :tailwind,
22
+ type: :boolean,
23
+ default: false,
24
+ desc: "Style the generated HelloWorld example with Tailwind CSS v4"
25
+
21
26
  class_option :new_app,
22
27
  type: :boolean,
23
28
  default: false,
@@ -25,17 +30,24 @@ module ReactOnRails
25
30
 
26
31
  def copy_base_files
27
32
  base_js_path = "base/base"
33
+ tailwind_js_path = "base/tailwind"
34
+ ext = component_extension(options)
28
35
 
29
36
  # Determine which component files to copy based on TypeScript option
30
- component_files = [
31
- "app/javascript/src/HelloWorld/ror_components/HelloWorld.client.#{component_extension(options)}",
32
- "app/javascript/src/HelloWorld/ror_components/HelloWorld.server.#{component_extension(options)}",
33
- "app/javascript/src/HelloWorld/ror_components/HelloWorld.module.css"
34
- ]
35
-
36
- component_files.each do |file|
37
- copy_file("#{base_js_path}/#{file}", file)
37
+ client_component =
38
+ "app/javascript/src/HelloWorld/ror_components/HelloWorld.client.#{ext}"
39
+ server_component =
40
+ "app/javascript/src/HelloWorld/ror_components/HelloWorld.server.#{ext}"
41
+
42
+ if use_tailwind?
43
+ copy_file("#{tailwind_js_path}/#{client_component}", client_component)
44
+ else
45
+ copy_file("#{base_js_path}/#{client_component}", client_component)
46
+ copy_file("#{base_js_path}/app/javascript/src/HelloWorld/ror_components/HelloWorld.module.css",
47
+ "app/javascript/src/HelloWorld/ror_components/HelloWorld.module.css")
38
48
  end
49
+
50
+ copy_file("#{base_js_path}/#{server_component}", server_component)
39
51
  end
40
52
 
41
53
  def create_appropriate_templates
@@ -35,6 +35,11 @@ module ReactOnRails
35
35
  default: false,
36
36
  hide: true
37
37
 
38
+ class_option :tailwind,
39
+ type: :boolean,
40
+ default: false,
41
+ hide: true
42
+
38
43
  def create_redux_directories
39
44
  # Create auto-bundling directory structure for Redux
40
45
  empty_directory("app/javascript/src/HelloWorldApp/ror_components")
@@ -53,18 +58,33 @@ module ReactOnRails
53
58
  "app/javascript/src/HelloWorldApp/ror_components/HelloWorldApp.client.#{ext}")
54
59
  copy_file("#{base_js_path}/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server.#{ext}",
55
60
  "app/javascript/src/HelloWorldApp/ror_components/HelloWorldApp.server.#{ext}")
56
- copy_file("#{base_js_path}/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css",
57
- "app/javascript/src/HelloWorldApp/components/HelloWorld.module.css")
61
+
62
+ unless use_tailwind?
63
+ copy_file("#{base_js_path}/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css",
64
+ "app/javascript/src/HelloWorldApp/components/HelloWorld.module.css")
65
+ end
58
66
 
59
67
  # Update import paths in client component
60
68
  ror_client_file = "app/javascript/src/HelloWorldApp/ror_components/HelloWorldApp.client.#{ext}"
61
69
  gsub_file(ror_client_file, "../store/helloWorldStore", "../store/helloWorldStore")
62
70
  gsub_file(ror_client_file, "../containers/HelloWorldContainer",
63
71
  "../containers/HelloWorldContainer")
72
+ return unless use_tailwind?
73
+
74
+ stylesheet_import = "import '../../../stylesheets/application.css';\n"
75
+ ror_client_file_path = File.join(destination_root, ror_client_file)
76
+ if options[:pretend]
77
+ say_status :pretend, "Would add Tailwind stylesheet import to #{ror_client_file}", :yellow
78
+ return
79
+ end
80
+ return if File.read(ror_client_file_path).include?(stylesheet_import)
81
+
82
+ prepend_to_file(ror_client_file, stylesheet_import)
64
83
  end
65
84
 
66
85
  def copy_base_redux_files
67
86
  base_hello_world_path = "redux/base/app/javascript/bundles/HelloWorld"
87
+ tailwind_hello_world_path = "redux/tailwind/app/javascript/bundles/HelloWorld"
68
88
  redux_extension = options.typescript? ? "ts" : "js"
69
89
 
70
90
  # Copy Redux infrastructure files with appropriate extension
@@ -72,11 +92,15 @@ module ReactOnRails
72
92
  containers/HelloWorldContainer.#{redux_extension}
73
93
  constants/helloWorldConstants.#{redux_extension}
74
94
  reducers/helloWorldReducer.#{redux_extension}
75
- store/helloWorldStore.#{redux_extension}
76
- components/HelloWorld.#{component_extension(options)}].each do |file|
95
+ store/helloWorldStore.#{redux_extension}].each do |file|
77
96
  copy_file("#{base_hello_world_path}/#{file}",
78
97
  "app/javascript/src/HelloWorldApp/#{file}")
79
98
  end
99
+
100
+ component_file = "components/HelloWorld.#{component_extension(options)}"
101
+ component_source_path = use_tailwind? ? tailwind_hello_world_path : base_hello_world_path
102
+ copy_file("#{component_source_path}/#{component_file}",
103
+ "app/javascript/src/HelloWorldApp/#{component_file}")
80
104
  end
81
105
 
82
106
  def create_appropriate_templates
@@ -42,6 +42,11 @@ module ReactOnRails
42
42
  default: false,
43
43
  hide: true
44
44
 
45
+ class_option :tailwind,
46
+ type: :boolean,
47
+ default: false,
48
+ hide: true
49
+
45
50
  def run_generator
46
51
  # When invoked by install_generator, skip prerequisites (parent already validated)
47
52
  if options[:invoked_by_install] || prerequisites_met?
@@ -206,7 +206,13 @@ module ReactOnRails
206
206
  # Check if HelloServer already exists (check both jsx and tsx)
207
207
  if File.exist?(File.join(destination_root, "#{ror_components_dir}/HelloServer.jsx")) ||
208
208
  File.exist?(File.join(destination_root, "#{ror_components_dir}/HelloServer.tsx"))
209
- say "ℹ️ HelloServer component already exists, skipping", :yellow
209
+ tailwind_import_added = add_tailwind_import_to_rsc_client_component(components_dir)
210
+ message = if tailwind_import_added
211
+ "ℹ️ HelloServer component already exists; added Tailwind stylesheet import to LikeButton"
212
+ else
213
+ "ℹ️ HelloServer component already exists, skipping"
214
+ end
215
+ say message, :yellow
210
216
  return
211
217
  end
212
218
 
@@ -223,10 +229,38 @@ module ReactOnRails
223
229
  "#{components_dir}/HelloServer.#{ext}")
224
230
  copy_file("templates/rsc/base/app/javascript/src/HelloServer/components/LikeButton.#{ext}",
225
231
  "#{components_dir}/LikeButton.#{ext}")
232
+ add_tailwind_import_to_rsc_client_component(components_dir)
226
233
 
227
234
  say "✅ Created HelloServer component", :green
228
235
  end
229
236
 
237
+ def add_tailwind_import_to_rsc_client_component(components_dir)
238
+ return false unless use_tailwind?
239
+
240
+ candidate_entry_paths = %w[jsx tsx].map do |extension|
241
+ "#{components_dir}/LikeButton.#{extension}"
242
+ end
243
+
244
+ relative_entry_path = candidate_entry_paths.find do |entry_path|
245
+ File.exist?(File.join(destination_root, entry_path))
246
+ end
247
+ return false unless relative_entry_path
248
+
249
+ # Path is relative to app/javascript/src/HelloServer/components/.
250
+ stylesheet_import = "import '../../../stylesheets/application.css';"
251
+ entry_path = File.join(destination_root, relative_entry_path)
252
+ entry_content = File.read(entry_path)
253
+ return false if entry_content.include?(stylesheet_import)
254
+
255
+ client_directive_pattern = /\A\s*['"]use client['"];?[^\S\r\n]*(?:\r?\n|$)/
256
+ if entry_content.match?(client_directive_pattern)
257
+ insert_into_file(relative_entry_path, "#{stylesheet_import}\n", after: client_directive_pattern)
258
+ else
259
+ prepend_to_file(relative_entry_path, "#{stylesheet_import}\n")
260
+ end
261
+ true
262
+ end
263
+
230
264
  def create_hello_server_controller
231
265
  controller_path = "app/controllers/hello_server_controller.rb"
232
266
 
@@ -0,0 +1,11 @@
1
+ ---
2
+ description: React on Rails conventions for this app (components, react_component helper, SSR bundles, errors)
3
+ globs:
4
+ alwaysApply: true
5
+ ---
6
+
7
+ This app uses **React on Rails**. For React on Rails conventions — adding and
8
+ registering components, the `react_component` view helper, `.client`/`.server`
9
+ bundle rules, the `ReactOnRails` JS API, common errors and fixes, and the
10
+ `bin/rails react_on_rails:doctor` diagnostic — read **AGENTS.md** in the project
11
+ root.
@@ -0,0 +1,7 @@
1
+ # GitHub Copilot instructions
2
+
3
+ This app uses **React on Rails**. For React on Rails conventions — adding and
4
+ registering components, the `react_component` view helper, `.client`/`.server`
5
+ bundle rules, the `ReactOnRails` JS API, common errors and fixes, and the
6
+ `bin/rails react_on_rails:doctor` diagnostic — read **AGENTS.md** in the project
7
+ root.
@@ -0,0 +1,164 @@
1
+ # AGENTS.md — React on Rails (for AI coding agents working IN this app)
2
+
3
+ This app uses **React on Rails** to render React components from Rails views, with
4
+ optional server-side rendering (SSR). This file teaches an AI agent the conventions
5
+ it needs to add, register, and render a component correctly on the first try.
6
+
7
+ It is intentionally short. For the full reference, see the links at the bottom.
8
+
9
+ > This file describes an app that **uses** React on Rails. It is not the
10
+ > contributor guide for the React on Rails framework itself.
11
+
12
+ ## 1. Adding a component (auto-bundling — the default)
13
+
14
+ Place a component under any directory named `ror_components`. React on Rails
15
+ auto-bundles it, so **no manual registration is needed**:
16
+
17
+ ```
18
+ app/javascript/src/<Name>/ror_components/<Name>.tsx # or .jsx
19
+ ```
20
+
21
+ The component file **must `export default`** the React component. Example:
22
+
23
+ ```tsx
24
+ // app/javascript/src/SimpleCounter/ror_components/SimpleCounter.tsx
25
+ import React, { useState } from 'react';
26
+
27
+ const SimpleCounter = (props: { initialCount?: number }) => {
28
+ const [count, setCount] = useState(props.initialCount ?? 0);
29
+ return <button onClick={() => setCount((c) => c + 1)}>Count: {count}</button>;
30
+ };
31
+
32
+ export default SimpleCounter; // default export is required
33
+ ```
34
+
35
+ ### Manual registration (alternative)
36
+
37
+ If you are not using the `ror_components/` auto-bundling convention, register the
38
+ component explicitly from a pack/entry file, importing from the `react-on-rails`
39
+ npm package:
40
+
41
+ ```ts
42
+ import ReactOnRails from 'react-on-rails';
43
+ import SimpleCounter from './SimpleCounter';
44
+
45
+ ReactOnRails.register({ SimpleCounter });
46
+ ```
47
+
48
+ The key (`SimpleCounter`) is the name you pass to `react_component` in the Rails view.
49
+
50
+ ## 2. Rendering it from a Rails view
51
+
52
+ Use the `react_component` view helper in any `.html.erb` view (e.g.
53
+ `app/views/<controller>/<action>.html.erb`). The first argument is the registered
54
+ component name; it must match the file/registration name exactly (case-sensitive).
55
+
56
+ ```erb
57
+ <%= react_component("SimpleCounter", props: { initialCount: 5 }, prerender: true) %>
58
+ ```
59
+
60
+ - `props:` — a Ruby Hash serialized to JSON and passed to the component.
61
+ - `prerender:` — `true` renders the component on the server (SSR) then hydrates on
62
+ the client; `false` renders only on the client. Set `prerender: false` to isolate
63
+ SSR issues while debugging.
64
+
65
+ ## 3. `.client` vs `.server` (and plain) bundles — what runs where
66
+
67
+ When a component name resolves to multiple files, React on Rails picks by suffix:
68
+
69
+ - `<Name>.client.tsx` — runs in the **browser** only. May use `window`, `document`,
70
+ browser-only APIs, and client-side hooks/effects.
71
+ - `<Name>.server.tsx` — runs during **server-side rendering** (Node, no DOM). It
72
+ **must not** use browser-only globals (`window`, `document`, `localStorage`).
73
+ Often it just re-exports the client component for SSR.
74
+ - `<Name>.tsx` (plain, no suffix) — used for **both** server and client. Keep it
75
+ isomorphic: guard any browser-only code with `if (typeof window !== 'undefined')`.
76
+
77
+ Rule of thumb: if `prerender: true`, the code path must run in Node without a DOM.
78
+
79
+ ## 4. The `ReactOnRails` JS API you will actually touch
80
+
81
+ Import from the `react-on-rails` npm package:
82
+
83
+ ```ts
84
+ import ReactOnRails from 'react-on-rails';
85
+ ```
86
+
87
+ - `ReactOnRails.register({ Name })` — register one or more components by name.
88
+ - `ReactOnRails.registerStore({ name })` / `ReactOnRails.getStore(name)` — register
89
+ and retrieve a Redux store (only if this app uses Redux).
90
+ - `ReactOnRails.reactOnRailsPageLoaded()` — rarely needed; React on Rails wires
91
+ client hydration automatically.
92
+
93
+ You usually only need `register` (and only when not using `ror_components/`).
94
+
95
+ ## 5. Top errors and fixes
96
+
97
+ These messages come straight from React on Rails' runtime errors. When you hit one,
98
+ apply the matching fix.
99
+
100
+ ### `Component '<Name>' Not Registered`
101
+
102
+ The component name in the view does not match a registered/auto-bundled component.
103
+
104
+ - Recommended (auto-bundling): put the component file directly inside a
105
+ `ror_components/` directory, e.g.
106
+ `app/javascript/src/<Name>/ror_components/<Name>.client.tsx`, with a `default`
107
+ export, then regenerate packs: `bin/rails react_on_rails:generate_packs`.
108
+ - Or register manually: `ReactOnRails.register({ <Name>: <Name> });` and
109
+ `import <Name> from './components/<Name>';`.
110
+ - Check the name matches the `react_component("<Name>", ...)` call exactly (case-sensitive).
111
+
112
+ ### `Auto-loaded Bundle Missing`
113
+
114
+ The component is set up for auto-loading but its bundle is missing.
115
+
116
+ 1. Run the pack generation task: `bin/rails react_on_rails:generate_packs`.
117
+ 2. Ensure the component is in the correct directory under `app/javascript`.
118
+ 3. Check naming conventions: file is `<Name>.jsx` or `<Name>.tsx` and `export default`s.
119
+ 4. Verify nested entries are enabled in your Shakapacker/webpack config.
120
+
121
+ ### `Hydration Mismatch`
122
+
123
+ Server-rendered HTML doesn't match what React rendered on the client.
124
+
125
+ 1. **Random IDs or timestamps**: use props or deterministic values, not
126
+ `Math.random()` / `Date.now()`.
127
+ 2. **Browser-only APIs**: guard with `if (typeof window !== 'undefined') { ... }`.
128
+ 3. **Different data**: ensure props/`railsContext`/store init are identical on
129
+ server and client.
130
+ 4. Temporarily set `prerender: false` to isolate the issue.
131
+
132
+ ### `Server Rendering Failed`
133
+
134
+ An error occurred while server-rendering a component.
135
+
136
+ 1. Check JS console output in the Rails log: `tail -f log/development.log | grep 'React on Rails'`.
137
+ 2. Common causes: missing Node dependencies, syntax errors, or browser-only APIs in
138
+ server code.
139
+ 3. Set `config.trace = true` and check `config.server_bundle_js_file` points at the
140
+ correct file.
141
+
142
+ ### `Redux Store Not Found`
143
+
144
+ A Redux store wasn't registered before a component that depends on it rendered.
145
+
146
+ 1. Register the store: `ReactOnRails.registerStore({ <store> });`.
147
+ 2. Initialize it in the view before the component: `<%= redux_store('<store>', props: {}) %>`.
148
+ 3. Declare the dependency: `store_dependencies: ['<store>']`.
149
+
150
+ ## 6. Diagnose your setup
151
+
152
+ Before guessing, run the doctor — it checks configuration, bundles, and dependencies:
153
+
154
+ ```bash
155
+ bin/rails react_on_rails:doctor
156
+ ```
157
+
158
+ ## 7. Where the full reference lives
159
+
160
+ - Hosted docs: https://reactonrails.com/docs/
161
+ - Getting-started tutorial: https://reactonrails.com/docs/getting-started/tutorial/
162
+ - Machine-readable route map / expanded reference (for agents):
163
+ https://github.com/shakacode/react_on_rails/blob/main/llms.txt and
164
+ https://github.com/shakacode/react_on_rails/blob/main/llms-full.txt
@@ -0,0 +1,8 @@
1
+ # CLAUDE.md
2
+
3
+ This app uses **React on Rails**. For React on Rails conventions — adding and
4
+ registering components, the `react_component` view helper, `.client`/`.server`
5
+ bundle rules, the `ReactOnRails` JS API, common errors and fixes, and the
6
+ `bin/rails react_on_rails:doctor` diagnostic — read **[AGENTS.md](./AGENTS.md)**.
7
+
8
+ Follow any project-specific instructions elsewhere in this file or repository.