ruact 0.0.12 → 0.0.14

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +59 -1
  3. data/README.md +4 -4
  4. data/lib/generators/ruact/install/install_generator.rb +418 -128
  5. data/lib/generators/ruact/install/templates/AGENTS.md.tt +14 -13
  6. data/lib/generators/ruact/install/templates/Procfile.dev.tt +1 -1
  7. data/lib/generators/ruact/install/templates/initializer.rb.tt +29 -7
  8. data/lib/generators/ruact/install/templates/package.json.tt +4 -4
  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/scaffold_generator.rb +39 -12
  12. data/lib/generators/ruact/scaffold/scaffold_shadcn_preflight.rb +4 -3
  13. data/lib/generators/ruact/scaffold/templates/components/List.tsx.tt +8 -8
  14. data/lib/generators/ruact/scaffold/templates/components/agnostic/List.tsx.tt +8 -8
  15. data/lib/generators/ruact/scaffold/templates/controller.rb.tt +5 -3
  16. data/lib/generators/ruact/scaffold/templates/queries/query.rb.tt +2 -2
  17. data/lib/generators/ruact/scaffold/templates/views/index.html.erb.tt +1 -1
  18. data/lib/ruact/configuration.rb +71 -17
  19. data/lib/ruact/controller/document_rendering.rb +72 -16
  20. data/lib/ruact/controller/page_rendering.rb +134 -0
  21. data/lib/ruact/controller/pages.rb +116 -0
  22. data/lib/ruact/controller.rb +78 -11
  23. data/lib/ruact/doctor.rb +233 -25
  24. data/lib/ruact/layout_source.rb +29 -7
  25. data/lib/ruact/navigation_boundary.rb +240 -0
  26. data/lib/ruact/railtie.rb +30 -0
  27. data/lib/ruact/routing.rb +24 -6
  28. data/lib/ruact/server.rb +10 -1
  29. data/lib/ruact/version.rb +1 -1
  30. data/lib/ruact/view_helper.rb +158 -1
  31. data/lib/ruact/views/layouts/ruact.html.erb +32 -0
  32. data/lib/ruact.rb +29 -0
  33. data/vendor/javascript/vite-plugin-ruact/ruact-router.test.mjs +433 -0
  34. data/vendor/javascript/vite-plugin-ruact/runtime/ruact-router.js +170 -7
  35. data/vendor/javascript/vite-plugin-ruact/tsconfig.scaffold-agnostic.json +1 -1
  36. data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/PostList.tsx +3 -3
  37. data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/PostList.tsx +3 -3
  38. data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/ambient.d.ts +4 -4
  39. data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/ambient.d.ts +4 -4
  40. metadata +8 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4a8e7c8ef2e275bb38c837671dca78c5c2278378e95131e8e25b2f9edbb99339
4
- data.tar.gz: ab0713c0bcc7effd8d77bb6f961bcda55e8a233e124fd0669bc1133642632687
3
+ metadata.gz: '048ef204d79fa3c03ff9ab08c43e5d9d1fc1f42c0e92c7cf7311b510bc879b0a'
4
+ data.tar.gz: 67fdef7b25ac3ac2ea552eed361a45a62710bf28145ae7892d0e1f46f462aa47
5
5
  SHA512:
6
- metadata.gz: e301dbf4d7ee972f71d16beae5f972289542ecbedc353389dcbe6afe24b50f2393baf0445ebd152ef4da1114272a0a906805beb10921afe7944a318758ed38d6
7
- data.tar.gz: b69d9e0420b5b1dd7676bfc9870592efaee6438ce6ad944c4fda0d8e3173910e9c1ac6c8165d86bed6791153f801e6b990c194c422b62e3218e6716d200f0ee4
6
+ metadata.gz: cd6d63eaaa47a7cde090700c44ba53fe8cf9b29d82cc05ce2f672b98b01dd83dd82cb046b109cd47a328594344b1950f21d75fee5338870a0df5be438e86843e
7
+ data.tar.gz: 44b2880d6879617b00adc9a8ae070a1f722e147a782592c4b527e98dbe9af42d925f45c68daca3272df28a95c7a0c89d762164bf205c9da2dbf195e142e8e88d
data/CHANGELOG.md CHANGED
@@ -7,6 +7,62 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.0.14] - 2026-10-02
11
+
12
+ ### Fixed
13
+
14
+ - **Scaffolding a second resource stopped the app from booting.** Every `rails generate ruact:scaffold` wrote a query with a method named `search`. Query names share one namespace: each name gets one `GET /q/<name>` route and one export of `@/.ruact/server-functions`. So the second resource drew `ruact_query_search` again, and Rails refused to load the routes with "Invalid route name, already in use: 'ruact_query_search'", a route the app never wrote. The scaffold now names the method after the resource: `PostsQuery#search_posts`, served at `GET /q/searchPosts` and imported as `searchPosts`. Two query classes that define the same method — or one query class mounted twice — now raise `Ruact::ConfigurationError` naming the query, the path and the fix. The new name can meet one thing the bare `search` could not: a custom collection action named `search` on the same controller (`post :search, on: :collection`) also derives `searchPosts`, and boot stops with ruact's name-collision error, which points to `ruact_function_name` to rename one. Apps scaffolded before this release keep their `search` and work as they are. The first scaffold of a *new* resource in such an app no longer collides with it.
15
+
16
+ - **`rails generate ruact:install --shadcn` on an app that already had ruact installed left Tailwind out, then said the setup was in place.** That is the order Getting Started gives: install ruact first, add shadcn later. On the second run `package.json` and `Procfile.dev` already existed, so the generator skipped both. Tailwind was never declared and the `css` process was never added. `npx shadcn@latest init` then stopped with "No Tailwind CSS configuration found", right after the generator had printed that the prerequisites were in place. Under `--shadcn` the generator now adds what is missing to both files: the Tailwind `devDependencies` and the `build:css` script in `package.json`, the `css` process in `Procfile.dev`. It keeps every version and script you already declare; `package.json` is rewritten as 2-space JSON when something is added. When it cannot complete the setup, it says the setup is not in place and names what to do: a `package.json` it cannot parse, a Tailwind older than 4, or a `Procfile.dev` that already runs a `css` process or another Tailwind watcher — such as tailwindcss-rails' `bin/rails tailwindcss:watch`, which compiles its own entry rather than the `globals.css` shadcn writes its theme into. Without `--force`, `rails destroy ruact:install --shadcn` leaves an existing `package.json` in place.
17
+
18
+ - **Under `--shadcn`, `bin/dev` stopped as soon as it started when no terminal was attached.** The `css` process ran `@tailwindcss/cli --watch`, which stops watching and exits 0 when its input closes. Foreman then stops Rails and Vite as well. A coding agent, CI, Docker or an IDE task runner starts `bin/dev` without a terminal, so the app was gone before its first request, with no error in the output. The process now runs with `--watch=always`. To fix an app generated before this release, change `--watch` to `--watch=always` on the `css:` line of `Procfile.dev`.
19
+
20
+ - **The `ruact:scaffold --shadcn` pre-flight printed `npx shadcn@latest init` without `--base radix`.** The current shadcn defaults to Base UI, but the components the scaffold generates import Radix. An app set up from that message got components whose imports do not resolve. The message now prints `--base radix`, as the install generator already did.
21
+
22
+ - **Two generator messages linked to pages that do not exist.** The unknown-attribute-type error and the shadcn version warning pointed at a documentation path inside this repository, where no such file exists. They now link to ruact.dev.
23
+
24
+ ### Changed
25
+
26
+ - **The scaffold's list prop is named after the resource.** `<CommentList posts={rows} />` was a leftover from the reference app the templates came from, and every resource got a `posts` prop. It is now `<CommentList comments={rows} />`, `<BlogPostList blogPosts={rows} />`, and so on. Inside the component the prop is bound to a fixed local, so a model whose plural is one of the List's own names (`Row`) or one a module cannot declare (`Argument`) still compiles. A `Post` scaffold's view is unchanged.
27
+
28
+ - **shadcn 4 is in the default `shadcn_compatible_versions`** (now `[1, 2, 4]`). It is the current major, so every app that followed the setup steps saw `ruact:scaffold --shadcn` warn that its shadcn was not tested. shadcn 4 was run end to end with this release: `init --base radix`, the full `add` list, `ruact:scaffold --shadcn`, and the form, table and delete dialog checked in a browser.
29
+
30
+ ## [0.0.13] - 2026-09-27
31
+
32
+ ### Added
33
+
34
+ - **A development warning when a Turbo form gets a ruact page back.** A form on a Turbo page that posts to a ruact action is fetched by Turbo; Turbo does not show the ruact page that answers (a `422` with validation errors, say): it reloads the form's page, drops the answer or pastes the document into its own, depending on the status. In development and test ruact now logs a `[ruact]` warning naming the action, the request and the fix: `data-turbo="false"` on that form, so the browser submits it and shows the answer.
35
+
36
+ ### Fixed
37
+
38
+ - **`render :new, status: :unprocessable_entity` answered 500 on a ruact page.** The Rails idiom for a failed save rendered the template outside ruact, and its first client component raised `__ruact_component__ called outside a ruact_render flow`, with no hint of the fix. A `render` whose template is a ruact page of the same controller — `render :new`, `render "new"`, `render action:`, `render template: "posts/new"` — now goes through ruact, with its `status:`, `locals:`, `location:`, `variant:`, `locale:` and `assigns:` — when that `.html.erb` (or a locale/variant of it) is the template Rails would pick for the request, so a `.json` URL or a `format.json` branch still renders the JSON template. Every other `render` (other renderers, another folder's template, an action outside `ruact_pages`, an option ruact does not take such as wicked_pdf's `pdf:` or a stray `alert:`) is Rails' own, as before — and when that leaves a client component outside ruact, the error names the option. `ruact_render` takes `status:` too. A client component in a template Rails renders on its own now raises an error naming the component, the template and the call that renders it through ruact. The client router renders a Flight `422` in place — the errors are the page, not a failed request — and keeps the form's URL; any other failing status is still an error.
39
+
40
+ - **`respond_with` (the `responders` gem, Devise's controllers) raised `NoMethodError: private method 'redirect_to'` in a controller with `Ruact::Controller`.** ruact's `redirect_to` override was private, and `responders` calls `controller.redirect_to` from outside. It is public now, as Rails' own is — and so is the new `render` override.
41
+
42
+ - **A server-function call's validation errors were lost on `redirect_to`.** An action called from React that registers `ruact_errors(@post)` and then `redirect_to`s answers `{"$redirect": …}`; the page the runtime navigated to was documented to receive those errors, and never did, in any include order. They now ride the flash to it (a same-origin redirect; a redirect to another origin carries none).
43
+
44
+ - **Clicking from a ruact page to an ordinary Rails page did nothing.** The ruact router intercepts every same-origin link and asked for a ruact response; an ordinary Rails action answered with HTML the router could not render, so the click went nowhere — no page, no URL change, no error. Now the server tells the router, before running anything, when a route is not a ruact page, and the browser loads it normally. A form that posts to such a page is submitted by the browser the same way, so the action runs once and its response — a validation error included — is what you see. `Ruact::NavigationBoundary::Middleware` does this; remove it with `config.middleware.delete Ruact::NavigationBoundary::Middleware` if you need to.
45
+
46
+ - **Turbo and ruact stopped trampling each other.** After one round trip between a Turbo page and a ruact page, Turbo's links could go dead, a ruact page could render blank, or the address bar could show one page while the screen showed another. Every page ruact renders now carries `<meta name="turbo-visit-control" content="reload">`, so Turbo loads it in full instead of swapping it into its own document, and each document keeps its own router. It also carries `<meta name="turbo-prefetch" content="false">`: in a layout that loads Turbo 8, hovering a link on a ruact page no longer makes Turbo fetch — and run — the destination for a click it will never handle.
47
+
48
+ - **Styling from an npm component worked in development and disappeared in production.** A `"use client"` component that imports CSS — its own, or one a package ships, like a date picker's — produces a separate stylesheet that Vite builds, digest-stamps and serves. Nothing linked it. The page came back with the script tag and no `<link>`, so the component rendered unstyled: in the case that surfaced this ([#63](https://github.com/luizcg/ruact/issues/63)), a date picker collapsed into a column of overlapping numbers. Development hid it completely, because the Vite dev server injects that CSS through JavaScript.
49
+
50
+ A new `<head>` helper, `ruact_head_assets`, links those stylesheets — above your own, so your CSS loads last and wins ties. It emits nothing while the Vite dev server is running. The layout ruact now ships calls it for you, and so does the built-in shell (`config.layout = false`), which still carries none of *your* stylesheets. `rails ruact:doctor` fails when a build emits component CSS that the layout rendering your pages never links, and names that layout.
51
+
52
+ - **The generated `--shadcn` `tsconfig.json` had no types for a CSS import.** A client component importing a stylesheet for its side effect had nothing to resolve against. It now declares `vite/client`. If you keep your own `tsconfig.json`, add `"types": ["vite/client"]` to it.
53
+
54
+ ### Changed
55
+
56
+ - **Installing ruact no longer turns your whole app into ruact pages.** `rails generate ruact:install` used to add `include Ruact::Controller` to `ApplicationController`, and from then on every action with an `.html.erb` rendered through ruact — install it into an existing app to try one screen and the other eighty were converted too, Turbo Frames included. Now the install changes no controller: a page renders through ruact when its controller has `include Ruact::Controller`, and `ruact_pages only: %i[show]` (or `except:`) narrows that to some actions — an action declared there counts even when it renders another action's template. The navigation boundary reads the same declaration, so a form posting to an action left out of `ruact_pages` is submitted by the browser like any Rails form — and it is plain Rails in how it shows validation errors, too. A name in `ruact_pages only:` that is neither a method nor a template of the controller declaring it raises `Ruact::ConfigurationError` — a typo does not quietly make a page plain Rails. `rails generate ruact:scaffold` adds the include to the controllers it generates.
57
+
58
+ To make every page a ruact page, as before, run `rails generate ruact:install --app`: it puts the include on `ApplicationController` and renders through your own layout. **Apps already installed keep their include** — re-running the install says the app is in whole-app mode and how to leave it, and removes nothing. `rails ruact:doctor` now reports which mode the app is in, and in whole-app mode how many templates render through ruact.
59
+
60
+ - **New installs render ruact pages through a layout the gem ships, and no longer edit yours.** `rails generate ruact:install` used to write the React root and `ruact_js_assets` into `app/views/layouts/application.html.erb`. It now writes `config.layout = "ruact"`: ruact pages render through `layouts/ruact`, which comes with the gem and links CSRF, CSP, your client components' CSS and then the stylesheets you name in the new `config.layout_stylesheets` (`[:app]` under Propshaft 0.9+; the install writes `["application"]` on Sprockets or an older Propshaft, and `[]` with neither). Your layouts are not touched.
61
+
62
+ Because it is a named layout, it applies to every ruact page whatever a controller declares with `layout` (`layout false` included), and its `<title>` is your app's name on every page. What that layout does **not** bring is the rest of your own layout's `<head>` — favicons, fonts, analytics, meta tags other gems write — or your app's JavaScript. If a ruact page needs those, `rails generate ruact:layout` copies the layout into `app/views/layouts/ruact.html.erb`, where it is yours to change and wins over the gem's with no setting. Or set `config.layout = true` to keep rendering through your own layout, as before.
63
+
64
+ **Existing installs are not changed**: re-running the install leaves a `config.layout` you already have alone. On an app that renders through its own layout, the install now prints any of the three lines that layout is missing — `ruact_head_assets` is the new one — instead of writing them.
65
+
10
66
  ## [0.0.12] - 2026-09-09
11
67
 
12
68
  ### Added
@@ -320,7 +376,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
320
376
  - **CI matrix** — GitHub Actions: RSpec across Ruby 3.2 × 3.3 × Rails 7.0 × 7.1 × 7.2 × 8.0; RuboCop; YARD docs; memory benchmark; E2E system tests against React 19.0.0 and 19.x (Capybara + Cuprite); non-blocking React@next job with auto-issue on failure.
321
377
  - **E2E test app** — `e2e/` Rails app (no DB, in-memory Post model) with full CRUD system tests validating the complete request cycle.
322
378
 
323
- [Unreleased]: https://github.com/luizcg/ruact/compare/v0.0.12...HEAD
379
+ [Unreleased]: https://github.com/luizcg/ruact/compare/v0.0.14...HEAD
380
+ [0.0.14]: https://github.com/luizcg/ruact/releases/tag/v0.0.14
381
+ [0.0.13]: https://github.com/luizcg/ruact/releases/tag/v0.0.13
324
382
  [0.0.12]: https://github.com/luizcg/ruact/releases/tag/v0.0.12
325
383
  [0.0.11]: https://github.com/luizcg/ruact/releases/tag/v0.0.11
326
384
  [0.0.10]: https://github.com/luizcg/ruact/releases/tag/v0.0.10
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
 
@@ -98,7 +98,7 @@ Every item below is shipped in this gem at v0.0.11:
98
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)
99
99
  - **Client-side navigation** — link interception, scroll restoration and redirect-after-POST, derived from your Rails routes. [Docs](https://ruact.dev/docs/concepts/navigation)
100
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)
101
- - **`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)
102
102
  - **One runtime dependency** — `nokogiri`. Rails itself is not a declared dependency of this gem.
103
103
 
104
104
  ## AI tools and coding agents
@@ -136,7 +136,7 @@ Everything lives at [ruact.dev](https://ruact.dev):
136
136
  - [Getting Started](https://ruact.dev/docs/getting-started) — from `rails new` to a rendered component
137
137
  - [Why ruact?](https://ruact.dev/docs/why-ruact) — where it sits next to Hotwire and Inertia
138
138
  - [Server functions & queries](https://ruact.dev/docs/api/server-actions) — the full request/response contract
139
- - [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
140
140
  - [Testing](https://ruact.dev/docs/guides/testing) — render assertions on the server side
141
141
  - [Changelog](CHANGELOG.md) — also published at [ruact.dev/docs/changelog](https://ruact.dev/docs/changelog)
142
142