ruact 0.0.11 → 0.0.13

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 (137) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +147 -48
  3. data/CONTRIBUTING.md +233 -0
  4. data/README.md +18 -8
  5. data/RELEASING.md +125 -139
  6. data/lib/generators/ruact/install/install_generator.rb +305 -126
  7. data/lib/generators/ruact/install/templates/AGENTS.md.tt +14 -13
  8. data/lib/generators/ruact/install/templates/initializer.rb.tt +29 -7
  9. data/lib/generators/ruact/install/templates/tsconfig.json.tt +3 -0
  10. data/lib/generators/ruact/layout/layout_generator.rb +52 -0
  11. data/lib/generators/ruact/scaffold/templates/controller.rb.tt +5 -3
  12. data/lib/ruact/configuration.rb +65 -13
  13. data/lib/ruact/controller/document_rendering.rb +72 -16
  14. data/lib/ruact/controller/page_rendering.rb +134 -0
  15. data/lib/ruact/controller/pages.rb +116 -0
  16. data/lib/ruact/controller.rb +78 -11
  17. data/lib/ruact/doctor.rb +233 -25
  18. data/lib/ruact/layout_source.rb +29 -7
  19. data/lib/ruact/navigation_boundary.rb +240 -0
  20. data/lib/ruact/packaging.rb +68 -0
  21. data/lib/ruact/railtie.rb +30 -0
  22. data/lib/ruact/server.rb +10 -1
  23. data/lib/ruact/version.rb +1 -1
  24. data/lib/ruact/view_helper.rb +158 -1
  25. data/lib/ruact/views/layouts/ruact.html.erb +32 -0
  26. data/lib/ruact.rb +29 -0
  27. data/vendor/javascript/vite-plugin-ruact/flight-client.test.mjs +321 -0
  28. data/vendor/javascript/vite-plugin-ruact/package-lock.json +11 -0
  29. data/vendor/javascript/vite-plugin-ruact/package.json +1 -0
  30. data/vendor/javascript/vite-plugin-ruact/ruact-router.test.mjs +433 -0
  31. data/vendor/javascript/vite-plugin-ruact/runtime/flight-client.js +14 -3
  32. data/vendor/javascript/vite-plugin-ruact/runtime/ruact-router.js +170 -7
  33. metadata +12 -107
  34. data/.codecov.yml +0 -31
  35. data/.github/workflows/ci.yml +0 -284
  36. data/.github/workflows/server-functions-bench.yml +0 -54
  37. data/.rubocop.yml +0 -107
  38. data/.rubocop_todo.yml +0 -63
  39. data/Rakefile +0 -10
  40. data/bench/server_functions_dispatch_bench.rb +0 -276
  41. data/bench/server_functions_dispatch_bench.results.md +0 -150
  42. data/docs/internal/README.md +0 -9
  43. data/docs/internal/decisions/server-functions-api.md +0 -2236
  44. data/spec/benchmarks/baseline.json +0 -12
  45. data/spec/benchmarks/render_pipeline_benchmark_spec.rb +0 -109
  46. data/spec/fixtures/flight/README.md +0 -136
  47. data/spec/fixtures/flight/array.txt +0 -1
  48. data/spec/fixtures/flight/as_json_object.txt +0 -2
  49. data/spec/fixtures/flight/bigint.txt +0 -1
  50. data/spec/fixtures/flight/boolean_false.txt +0 -1
  51. data/spec/fixtures/flight/boolean_true.txt +0 -1
  52. data/spec/fixtures/flight/client_component_with_props.txt +0 -2
  53. data/spec/fixtures/flight/client_reference.txt +0 -2
  54. data/spec/fixtures/flight/hash.txt +0 -1
  55. data/spec/fixtures/flight/infinity.txt +0 -1
  56. data/spec/fixtures/flight/nan.txt +0 -1
  57. data/spec/fixtures/flight/negative_infinity.txt +0 -1
  58. data/spec/fixtures/flight/nil.txt +0 -1
  59. data/spec/fixtures/flight/number_float.txt +0 -1
  60. data/spec/fixtures/flight/number_integer.txt +0 -1
  61. data/spec/fixtures/flight/react_element_no_props.txt +0 -1
  62. data/spec/fixtures/flight/redirect_row.txt +0 -1
  63. data/spec/fixtures/flight/serializable_object.txt +0 -2
  64. data/spec/fixtures/flight/string_basic.txt +0 -1
  65. data/spec/fixtures/flight/string_dollar_escape.txt +0 -1
  66. data/spec/fixtures/flight/undefined.txt +0 -1
  67. data/spec/fixtures/readme/children-error.html.erb +0 -3
  68. data/spec/fixtures/readme/children-error.txt +0 -1
  69. data/spec/fixtures/story_7_9_views/controller_request_spec_support/demo/show.html.erb +0 -3
  70. data/spec/fixtures/story_7_9_views/controller_request_spec_support/errors_demo/new.html.erb +0 -3
  71. data/spec/fixtures/story_7_9_views/controller_request_spec_support/exploding_layout_demo/show.html.erb +0 -3
  72. data/spec/fixtures/story_7_9_views/controller_request_spec_support/ghost_layout_demo/show.html.erb +0 -3
  73. data/spec/fixtures/story_7_9_views/controller_request_spec_support/layout_demo/show.html.erb +0 -3
  74. data/spec/fixtures/story_7_9_views/controller_request_spec_support/rootless_layout_demo/show.html.erb +0 -3
  75. data/spec/fixtures/story_7_9_views/controller_request_spec_support/unwired_layout_demo/show.html.erb +0 -3
  76. data/spec/fixtures/story_7_9_views/layouts/bare_host.html.erb +0 -16
  77. data/spec/fixtures/story_7_9_views/layouts/exploding_host.html.erb +0 -24
  78. data/spec/fixtures/story_7_9_views/layouts/rootless_host.html.erb +0 -15
  79. data/spec/fixtures/story_7_9_views/layouts/ruact_host.html.erb +0 -17
  80. data/spec/readme_demo_message_spec.rb +0 -67
  81. data/spec/readme_spec.rb +0 -282
  82. data/spec/ruact/client_manifest_spec.rb +0 -270
  83. data/spec/ruact/component_contract_spec.rb +0 -119
  84. data/spec/ruact/configuration_spec.rb +0 -518
  85. data/spec/ruact/controller_request_spec.rb +0 -671
  86. data/spec/ruact/controller_spec.rb +0 -343
  87. data/spec/ruact/doctor_spec.rb +0 -769
  88. data/spec/ruact/erb_preprocessor_hook_spec.rb +0 -55
  89. data/spec/ruact/erb_preprocessor_spec.rb +0 -361
  90. data/spec/ruact/errors_spec.rb +0 -93
  91. data/spec/ruact/flight/renderer_spec.rb +0 -133
  92. data/spec/ruact/flight/serializer_spec.rb +0 -494
  93. data/spec/ruact/html_converter_spec.rb +0 -375
  94. data/spec/ruact/install_generator_spec.rb +0 -1549
  95. data/spec/ruact/layout_source_spec.rb +0 -108
  96. data/spec/ruact/manifest_resolver_spec.rb +0 -174
  97. data/spec/ruact/query_request_spec.rb +0 -706
  98. data/spec/ruact/query_spec.rb +0 -105
  99. data/spec/ruact/railtie_spec.rb +0 -155
  100. data/spec/ruact/render_context_spec.rb +0 -58
  101. data/spec/ruact/render_pipeline_concurrency_spec.rb +0 -78
  102. data/spec/ruact/render_pipeline_spec.rb +0 -928
  103. data/spec/ruact/scaffold_generator_spec.rb +0 -1849
  104. data/spec/ruact/serializable_spec.rb +0 -179
  105. data/spec/ruact/server_bucket_request_spec.rb +0 -785
  106. data/spec/ruact/server_function_name_spec.rb +0 -53
  107. data/spec/ruact/server_functions/backtrace_cleaner_spec.rb +0 -63
  108. data/spec/ruact/server_functions/bucket_two_payload_spec.rb +0 -200
  109. data/spec/ruact/server_functions/codegen_spec.rb +0 -397
  110. data/spec/ruact/server_functions/error_payload_spec.rb +0 -222
  111. data/spec/ruact/server_functions/error_suggestion_spec.rb +0 -79
  112. data/spec/ruact/server_functions/introspection_spec.rb +0 -135
  113. data/spec/ruact/server_functions/name_bridge_spec.rb +0 -212
  114. data/spec/ruact/server_functions/query_context_spec.rb +0 -72
  115. data/spec/ruact/server_functions/query_source_spec.rb +0 -193
  116. data/spec/ruact/server_functions/railtie_integration_spec.rb +0 -215
  117. data/spec/ruact/server_functions/rake_spec.rb +0 -86
  118. data/spec/ruact/server_functions/route_source_spec.rb +0 -202
  119. data/spec/ruact/server_functions/snapshot_spec.rb +0 -96
  120. data/spec/ruact/server_functions/snapshot_writer_spec.rb +0 -71
  121. data/spec/ruact/server_rescue_request_spec.rb +0 -416
  122. data/spec/ruact/server_spec.rb +0 -179
  123. data/spec/ruact/server_upload_request_spec.rb +0 -311
  124. data/spec/ruact/signed_references_spec.rb +0 -164
  125. data/spec/ruact/string_distance_spec.rb +0 -38
  126. data/spec/ruact/tasks_json_introspection_spec.rb +0 -141
  127. data/spec/ruact/testing/have_ruact_component_spec.rb +0 -170
  128. data/spec/ruact/testing/no_production_load_spec.rb +0 -41
  129. data/spec/ruact/validation_errors_spec.rb +0 -116
  130. data/spec/ruact/view_helper_spec.rb +0 -131
  131. data/spec/spec_helper.rb +0 -77
  132. data/spec/support/fixtures/pixel.png +0 -0
  133. data/spec/support/flight_wire_parser.rb +0 -21
  134. data/spec/support/flight_wire_parser_spec.rb +0 -93
  135. data/spec/support/matchers/flight_fixture_matcher.rb +0 -130
  136. data/spec/support/matchers/flight_fixture_matcher_spec.rb +0 -250
  137. data/spec/support/rails_stub.rb +0 -115
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,233 @@
1
+ # Contributing to ruact
2
+
3
+ Thanks for wanting to change something here. This guide takes you from a fresh clone to an open pull
4
+ request, and everything it asks you to run, you can run — with this repository and nothing else.
5
+
6
+ ## What this repository is
7
+
8
+ `luizcg/ruact` is one repository holding three things that ship together:
9
+
10
+ | Directory | What lives there |
11
+ |---|---|
12
+ | `lib/` | the Ruby library — the ERB preprocessor, the render pipeline, the Flight wire format, the Rails integration, the generators and the Rake tasks |
13
+ | `vendor/javascript/vite-plugin-ruact/` | the Vite plugin — it scans for `"use client"`, emits the client manifest, and generates the typed server-function accessors |
14
+ | `vendor/javascript/ruact-server-functions-runtime/` | the browser-side runtime those generated accessors call |
15
+
16
+ The two JavaScript packages are **bundled, not published**. A host app never installs them from npm:
17
+ `rails generate ruact:install` writes a `vite.config.js` that imports the plugin by absolute path off the
18
+ installed gem. That has one consequence worth stating up front, because it is what makes the rest of this
19
+ document short — **pointing a Rails app at your checkout of the gem points it at your checkout of the Vite
20
+ plugin too.** There is nothing to link separately, and nothing else to clone. This repository has no
21
+ submodules.
22
+
23
+ ## Prerequisites
24
+
25
+ | Tool | Version | Why |
26
+ |---|---|---|
27
+ | Ruby | >= 3.2 | the floor in `ruact.gemspec`; CI runs 3.2 and 3.3 |
28
+ | Node.js | 20 | the JavaScript suite and the asset build only — ruact runs no Node process in production |
29
+ | Bundler | any recent | `gem install bundler` |
30
+
31
+ You do not need a Rails **app** to work on this gem, and you do not install Rails by hand: the `Gemfile`
32
+ pins it to `RAILS_VERSION`, defaulting to 8.0, so `bundle install` brings it. What keeps the suite fast is
33
+ that `spec/support/rails_stub.rb` loads only Rails' core and adds test-only writers on top — the request
34
+ cycle (`action_controller`, `action_view`) is loaded by the handful of specs that actually need it, and by
35
+ nothing else.
36
+
37
+ ## Setup
38
+
39
+ ```bash
40
+ git clone https://github.com/luizcg/ruact.git
41
+ cd ruact
42
+ bundle install
43
+ ```
44
+
45
+ That is the whole setup for a Ruby-side change. If you are touching the Vite plugin or the browser
46
+ runtime, install their dependencies too:
47
+
48
+ ```bash
49
+ cd vendor/javascript/ruact-server-functions-runtime && npm install
50
+ cd ../vite-plugin-ruact && npm ci
51
+ ```
52
+
53
+ The first install is not decoration: the plugin's test run reaches into the runtime package, which keeps
54
+ `react` in its own dependencies.
55
+
56
+ ## Running the checks
57
+
58
+ These are the jobs in [`.github/workflows/ci.yml`](.github/workflows/ci.yml), which runs on every push and
59
+ every pull request. Running them locally first is the difference between one round and four.
60
+
61
+ <!-- ci-jobs:begin -->
62
+
63
+ | CI job | What it checks |
64
+ |---|---|
65
+ | `rspec` | the Ruby suite, on Ruby 3.2/3.3 × Rails 7.0/7.1/7.2/8.0 — eight cells |
66
+ | `rubocop` | style, plus the three custom cops below |
67
+ | `yard` | every public method carries YARD documentation |
68
+ | `js` | the Vite plugin's vitest suite, including its Ruby↔JS byte-parity cases, and a type-level test over the generated accessors |
69
+ | `benchmark` | render-pipeline allocations against a recorded baseline |
70
+ | `name-propagation` | greps the tree for names retired before v0.1.0 |
71
+ | `gate` | that `CHANGELOG.md` gained something if this branch changed what the published gem contains, and that a version bump is one legal semver step to a version nothing has released |
72
+
73
+ <!-- ci-jobs:end -->
74
+
75
+ There is one more job, `release`, which publishes to RubyGems. It never runs on a pull request, and on a
76
+ push to `main` it publishes only when `lib/ruact/version.rb` names a version that carries no tag — which is
77
+ a thing a maintainer does deliberately, in a pull request of its own. See [RELEASING.md](RELEASING.md) for
78
+ the release process itself — this document deliberately describes none of it, so the two cannot drift apart.
79
+
80
+ Locally:
81
+
82
+ ```bash
83
+ bundle exec rspec
84
+ bundle exec rubocop --format github
85
+ rm -rf .yardoc doc && bundle exec yard --fail-on-warning
86
+ bundle exec rake benchmark:memory
87
+ bin/release-gate origin/main HEAD
88
+ ```
89
+
90
+ and, for the `js` job:
91
+
92
+ ```bash
93
+ cd vendor/javascript/vite-plugin-ruact
94
+ npm test
95
+ npm run typecheck
96
+ ```
97
+
98
+ To reproduce one matrix cell other than the default — `RAILS_VERSION` is what the eight `rspec` cells vary,
99
+ and unset means 8.0:
100
+
101
+ ```bash
102
+ RAILS_VERSION=7.2 bundle install && bundle exec rspec
103
+ ```
104
+
105
+ ## Trying your working tree in a real app
106
+
107
+ The suite is fast and the gates are strict, and neither tells you whether your change works in a Rails
108
+ app. This does, in about two minutes. `RUACT` is the absolute path to your clone.
109
+
110
+ ```bash
111
+ export RUACT=/absolute/path/to/your/ruact
112
+
113
+ rails new myapp --skip-javascript
114
+ cd myapp
115
+ printf '\ngem "ruact", path: "%s"\n' "$RUACT" >> Gemfile
116
+ bundle install
117
+ bin/rails generate ruact:install
118
+ bin/dev
119
+ ```
120
+
121
+ No published gem and no published npm package take part: the `path:` source points Bundler at your
122
+ checkout, and the generated `vite.config.js` imports the Vite plugin from inside it. Both halves of your
123
+ change are live.
124
+
125
+ Give it something to render — a component in `app/javascript/components/`:
126
+
127
+ ```jsx
128
+ "use client";
129
+ import { useState } from "react";
130
+
131
+ export function LikeButton({ likes }) {
132
+ const [count, setCount] = useState(likes);
133
+ return <button onClick={() => setCount(count + 1)}>{count} likes</button>;
134
+ }
135
+ ```
136
+
137
+ a controller action, and a view that uses the component by name:
138
+
139
+ ```erb
140
+ <h1>Home</h1>
141
+ <LikeButton likes={@likes} />
142
+ ```
143
+
144
+ `bin/dev` runs Rails and Vite together. Load the page and the button counts up.
145
+
146
+ ## Four gates that fail for reasons your diff does not show
147
+
148
+ Most of CI fails where you expect. These four do not, so they are worth knowing before they surprise you.
149
+
150
+ 1. **`spec/readme_spec.rb`** — `README.md` is pinned **byte for byte** in places: the quick-start block and
151
+ the demo image reference are compared against literals in the spec, the file is allowed exactly one raw
152
+ `<img>`, and a `path:` gem source is forbidden there because a reader who copies one installs nothing.
153
+ Edit the README casually and this goes red.
154
+
155
+ ⚠️ **One of its assertions is repository-wide despite living in a README spec.** It runs `git ls-files`
156
+ over the whole repository and fails if any tracked file is a `.gif`, `.mp4`, `.webm` or `.webp` — media
157
+ committed here would ship inside every built `.gem` and stay in the clone history forever. So **no
158
+ document in this repository can carry an image**, including this one. Media lives with the documentation
159
+ site and is referenced by absolute URL.
160
+
161
+ ⚠️ **YARD reads `README.md`.** Curly braces in its prose — `{@likes}`, `{Foo#bar}` — are link macros to
162
+ YARD, and an unresolvable one turns the `yard` job red for a README edit. Write them as `&#123;` and
163
+ `&#125;` there.
164
+
165
+ 2. **`spec/readme_demo_message_spec.rb`** — the second half of that pin, covering the demo's message.
166
+
167
+ 3. **`spec/benchmarks/render_pipeline_benchmark_spec.rb`** — an **allocation** baseline at ×1.20 tolerance
168
+ against `spec/benchmarks/baseline.json`, shared by matrix cells that legitimately differ by around 9%.
169
+ When it fails, read the header comment at the top of that file before doing anything: it explains how to
170
+ tell a real regression from accumulated drift, and how to regenerate the baseline if it is drift.
171
+
172
+ 4. **Three custom RuboCop cops**, in `lib/rubocop/cop/ruact/` and enabled in `.rubocop.yml` —
173
+ `Ruact/NoSharedState`, `Ruact/NoIoInFlight` and `Ruact/NoExtendSelf`. Between them they say that data
174
+ flows through explicit arguments rather than shared mutable state, that `lib/ruact/flight/**` stays a
175
+ pure value transformation, and that modules expose explicit class methods.
176
+
177
+ Custom cops are the least discoverable failure a newcomer can hit, which is why they are named here —
178
+ but **the cop source is the authority for what each one actually detects**, and each detects a subset of
179
+ the rule it is named for rather than the rule entire. `Ruact/NoExtendSelf` also rejects a bare
180
+ `module_function`, which its name does not suggest. Read the file before assuming a pattern is either
181
+ caught or allowed.
182
+
183
+ And one habit rather than a gate: run `yard` after `rm -rf .yardoc doc`, as the block above does. A warm
184
+ cache hides warnings that CI, which checks out fresh, still reports.
185
+
186
+ ## Where a change goes
187
+
188
+ Every destination below is one you can reach.
189
+
190
+ | Change | Where |
191
+ |---|---|
192
+ | anything under `lib/`, `app/`, `exe/`, `sig/`, `spec/` | **here** — `luizcg/ruact` |
193
+ | a typo in `README.md`, `CHANGELOG.md` or this file | **here** |
194
+ | the Vite plugin | **here**, at `vendor/javascript/vite-plugin-ruact/` — see the note below |
195
+ | the browser runtime for server functions | **here**, at `vendor/javascript/ruact-server-functions-runtime/` |
196
+ | the documentation site at `ruact.dev` | not in this repository — [open an issue](https://github.com/luizcg/ruact/issues) |
197
+ | roadmap, architecture decisions, design | not in this repository — [open an issue](https://github.com/luizcg/ruact/issues) or a discussion |
198
+
199
+ **About the Vite plugin.** There is a `vite-plugin-ruact` package on npm. It is **superseded**: nothing in
200
+ ruact installs it, and the plugin that actually runs is the vendored one in this repository. Send plugin
201
+ changes here.
202
+
203
+ ## Where the design work happens
204
+
205
+ Roadmap, architecture decisions and story planning live in a private repository, so the reasoning behind a
206
+ change is not always visible from here. That does not gate anything: open an issue or a discussion, and the
207
+ outcome of that thinking comes back to you in the thread — you do not need access to propose a change,
208
+ argue for one, or land one.
209
+
210
+ ## Opening the pull request
211
+
212
+ Branch off `main`, keep one topic per pull request, and open it against `luizcg/ruact`. Before you do:
213
+
214
+ - [ ] `bundle exec rspec` passes.
215
+ - [ ] `bundle exec rubocop --format github` is clean.
216
+ - [ ] `rm -rf .yardoc doc && bundle exec yard --fail-on-warning` passes, and every new public method has a
217
+ YARD comment with `@param` and `@return`.
218
+ - [ ] `# frozen_string_literal: true` is the first line of every new Ruby file.
219
+ - [ ] Nothing you added lives in `Ruact::`'s way: gem code stays under `Ruact::`, wire-format code under
220
+ `Ruact::Flight::`, and no Ruby or Rails core class is reopened.
221
+ - [ ] No `extend self` and no bare `module_function` — explicit class methods or a class instead.
222
+ - [ ] If you touched the Vite plugin or the runtime, `npm test` and `npm run typecheck` pass in
223
+ `vendor/javascript/vite-plugin-ruact`.
224
+ - [ ] `bin/release-gate origin/main HEAD` passes. If your branch changes anything that goes inside the
225
+ published gem, it asks [CHANGELOG.md](CHANGELOG.md) for a new bullet under `[Unreleased]` — written
226
+ for a reader who did not see the diff.
227
+
228
+ Describe what the change does and why in the pull request body. If it fixes a reported bug, link the issue.
229
+
230
+ Security issues do **not** go in a pull request — see [SECURITY.md](SECURITY.md) for private reporting.
231
+
232
+ By contributing you agree that your contribution is licensed under the MIT license in
233
+ [LICENSE.txt](LICENSE.txt).
data/README.md CHANGED
@@ -22,14 +22,14 @@ rails new myapp --skip-javascript && cd myapp
22
22
  # 2. Add the gem
23
23
  bundle add ruact
24
24
 
25
- # 3. Write the config, the layout wiring and an AGENTS.md — then run npm install
25
+ # 3. Write the config and an AGENTS.md (no layout of yours is edited) — then run npm install
26
26
  rails generate ruact:install
27
27
 
28
28
  # 4. Rails + Vite, one command
29
29
  bin/dev
30
30
  ```
31
31
 
32
- That is the whole install. The [Getting Started guide](https://ruact.dev/docs/getting-started) picks it up from here — first component, first scaffold, `ruact:doctor`. Already have an app? Start at step 2, then read [Progressive migration](https://ruact.dev/docs/guides/progressive-migration) — ruact renders one action at a time and leaves the rest of your views alone.
32
+ That is the whole install. The [Getting Started guide](https://ruact.dev/docs/getting-started) picks it up from here — first component, first scaffold, `ruact:doctor`. Already have an app? Start at step 2, then read [Progressive migration](https://ruact.dev/docs/guides/progressive-migration) — ruact renders only the controllers you include it in and leaves the rest of your views alone.
33
33
 
34
34
  ## How it works
35
35
 
@@ -58,7 +58,9 @@ export function PostCard({ post, author }) {
58
58
  }
59
59
  ```
60
60
 
61
- Rails serializes `@post` and `@author` as Ruby values, sends the component tree as a [Flight](https://ruact.dev/docs/concepts/flight-wire-format) payload, and React hydrates it in the browser. No JSON ceremony, no duplicate routes, no Node.js in production.
61
+ Capitalized tag means React. Lowercase stays HTML. `"use client"` is the only directive you need to learn.
62
+
63
+ Your ERB view renders on the server the way it always did, and ruact sends the result to the browser as a React tree — in the same [wire format](https://ruact.dev/docs/concepts/flight-wire-format) React uses for Server Components, implemented in Ruby. The data the view needs is inlined in the page, so React renders immediately, without a fetch. Node builds the bundle with Vite and is needed nowhere else.
62
64
 
63
65
  ## Call Rails from React
64
66
 
@@ -86,17 +88,17 @@ The verb decides — there is no per-action DSL and no second endpoint. The expo
86
88
 
87
89
  ## What you get
88
90
 
89
- Every item below is shipped in this gem at v0.0.9:
91
+ Every item below is shipped in this gem at v0.0.11:
90
92
 
91
93
  - **ERB as server components** — `include Ruact::Controller`, then use your components by name in the views you already have: capitalized is React, lowercase stays HTML. [Docs](https://ruact.dev/docs/concepts/erb-as-server-components)
92
94
  - **`"use client"`** — the one directive that marks a file as client-side. The bundled Vite plugin scans for it and writes the manifest. [Docs](https://ruact.dev/docs/concepts/use-client)
93
- - **Server functions and queries** — `include Ruact::Server` and `Ruact::Query` + `useQuery`, both reachable through a typed module generated from your route table. [Docs](https://ruact.dev/docs/api/server-actions)
94
- - **Props are an allowlist** — `include Ruact::Serializable` + `ruact_props :id, :title`; other columns never cross. [Docs](https://ruact.dev/docs/api/serializable)
95
+ - **Server functions and queries** — `include Ruact::Server` and `Ruact::Query` + `useQuery`, both reachable through a module generated from your route table. A query's declared keywords become an exact TypeScript signature; an action's accessor is typed to accept an object or a `FormData` and resolve whatever the action answers. [Docs](https://ruact.dev/docs/api/server-actions)
96
+ - **An opt-in allowlist for props** — by default a model prop is serialized with `as_json`, so every attribute crosses and the log says so. `include Ruact::Serializable` + `ruact_props :id, :title` makes it an allowlist, and `strict_serialization` (on in production) turns the permissive path into an error rather than a warning. [Docs](https://ruact.dev/docs/api/serializable)
95
97
  - **Validation errors round-trip** — `ruact_errors(record)` hands React `{ title: ["can't be blank"] }` without a serializer. [Docs](https://ruact.dev/docs/api/server-actions)
96
98
  - **Signed record references** — `Ruact.signed_global_id(record, for:, expires_in:)` out, `Ruact.locate_signed(token, for:)` back in; a tampered token is a `400`, not a lookup. [Docs](https://ruact.dev/docs/api/server-actions)
97
99
  - **Client-side navigation** — link interception, scroll restoration and redirect-after-POST, derived from your Rails routes. [Docs](https://ruact.dev/docs/concepts/navigation)
98
100
  - **A CRUD generator** — `rails generate ruact:scaffold Post title:string body:text` delegates the model, migration and route to Rails' own `resource` generator, then adds the ruact layer. Plain semantic HTML by default; `--shadcn` opts into the Tailwind/shadcn path. It does not run migrations — `rails db:migrate` is still yours. [Docs](https://ruact.dev/docs/api/scaffold)
99
- - **`bin/rails ruact:doctor`** — eight checks over the manifest, Vite, the layout and streaming; exits `1` when one fails. [Docs](https://ruact.dev/docs/api/ruact-doctor)
101
+ - **`bin/rails ruact:doctor`** — nine checks over the manifest, Vite, the layout, the client-component CSS and streaming; exits `1` when one fails. [Docs](https://ruact.dev/docs/api/ruact-doctor)
100
102
  - **One runtime dependency** — `nokogiri`. Rails itself is not a declared dependency of this gem.
101
103
 
102
104
  ## AI tools and coding agents
@@ -112,6 +114,12 @@ That is the whole adaptation, and it holds for any tool that outputs standard Re
112
114
 
113
115
  For agents driving the app rather than writing one component: `rails generate ruact:install` writes an `AGENTS.md` into your app, [ruact.dev/llms.txt](https://ruact.dev/llms.txt) serves the same context to tools that fetch from the web, and `bin/rails ruact:doctor -- --json` / `bin/rails ruact:routes -- --json` emit machine-readable output (experimental — `schema_version: 0`, and the `--` separator is required).
114
116
 
117
+ ## Where ruact fits
118
+
119
+ **A good fit:** a Rails monolith that needs real React on real screens — dashboards, editors, admin tools, anything with interaction that ERB alone makes painful — with one team shipping both sides and no appetite for a second application to keep in sync.
120
+
121
+ **Not a fit yet:** pages that must render without JavaScript, or content search engines need to read out of the HTML. ruact renders client-side from a payload inlined in the page; there is no server-rendered HTML for the React tree. That is a real gap, not a roadmap wink — if the page is your SEO surface, keep it in ERB (the two coexist in the same app, per-view) or use something that server-renders.
122
+
115
123
  ## Compatibility
116
124
 
117
125
  | | Version | Where that comes from |
@@ -128,7 +136,7 @@ Everything lives at [ruact.dev](https://ruact.dev):
128
136
  - [Getting Started](https://ruact.dev/docs/getting-started) — from `rails new` to a rendered component
129
137
  - [Why ruact?](https://ruact.dev/docs/why-ruact) — where it sits next to Hotwire and Inertia
130
138
  - [Server functions & queries](https://ruact.dev/docs/api/server-actions) — the full request/response contract
131
- - [Progressive migration](https://ruact.dev/docs/guides/progressive-migration) — adopting it one action at a time
139
+ - [Progressive migration](https://ruact.dev/docs/guides/progressive-migration) — adopting it one controller at a time
132
140
  - [Testing](https://ruact.dev/docs/guides/testing) — render assertions on the server side
133
141
  - [Changelog](CHANGELOG.md) — also published at [ruact.dev/docs/changelog](https://ruact.dev/docs/changelog)
134
142
 
@@ -136,6 +144,8 @@ Everything lives at [ruact.dev](https://ruact.dev):
136
144
 
137
145
  Bug reports and pull requests are welcome at [github.com/luizcg/ruact/issues](https://github.com/luizcg/ruact/issues).
138
146
 
147
+ Setup, the checks a PR runs, and how to try your working tree in a real Rails app: [CONTRIBUTING.md](CONTRIBUTING.md).
148
+
139
149
  Release process: [RELEASING.md](RELEASING.md). Security policy and private reporting: [SECURITY.md](SECURITY.md).
140
150
 
141
151
  ## License