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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +59 -1
- data/README.md +4 -4
- data/lib/generators/ruact/install/install_generator.rb +418 -128
- data/lib/generators/ruact/install/templates/AGENTS.md.tt +14 -13
- data/lib/generators/ruact/install/templates/Procfile.dev.tt +1 -1
- data/lib/generators/ruact/install/templates/initializer.rb.tt +29 -7
- data/lib/generators/ruact/install/templates/package.json.tt +4 -4
- data/lib/generators/ruact/install/templates/tsconfig.json.tt +3 -0
- data/lib/generators/ruact/layout/layout_generator.rb +52 -0
- data/lib/generators/ruact/scaffold/scaffold_generator.rb +39 -12
- data/lib/generators/ruact/scaffold/scaffold_shadcn_preflight.rb +4 -3
- data/lib/generators/ruact/scaffold/templates/components/List.tsx.tt +8 -8
- data/lib/generators/ruact/scaffold/templates/components/agnostic/List.tsx.tt +8 -8
- data/lib/generators/ruact/scaffold/templates/controller.rb.tt +5 -3
- data/lib/generators/ruact/scaffold/templates/queries/query.rb.tt +2 -2
- data/lib/generators/ruact/scaffold/templates/views/index.html.erb.tt +1 -1
- data/lib/ruact/configuration.rb +71 -17
- data/lib/ruact/controller/document_rendering.rb +72 -16
- data/lib/ruact/controller/page_rendering.rb +134 -0
- data/lib/ruact/controller/pages.rb +116 -0
- data/lib/ruact/controller.rb +78 -11
- data/lib/ruact/doctor.rb +233 -25
- data/lib/ruact/layout_source.rb +29 -7
- data/lib/ruact/navigation_boundary.rb +240 -0
- data/lib/ruact/railtie.rb +30 -0
- data/lib/ruact/routing.rb +24 -6
- data/lib/ruact/server.rb +10 -1
- data/lib/ruact/version.rb +1 -1
- data/lib/ruact/view_helper.rb +158 -1
- data/lib/ruact/views/layouts/ruact.html.erb +32 -0
- data/lib/ruact.rb +29 -0
- data/vendor/javascript/vite-plugin-ruact/ruact-router.test.mjs +433 -0
- data/vendor/javascript/vite-plugin-ruact/runtime/ruact-router.js +170 -7
- data/vendor/javascript/vite-plugin-ruact/tsconfig.scaffold-agnostic.json +1 -1
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/PostList.tsx +3 -3
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/PostList.tsx +3 -3
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/agnostic/ambient.d.ts +4 -4
- data/vendor/javascript/vite-plugin-ruact/type-tests/scaffold/ambient.d.ts +4 -4
- metadata +8 -2
data/lib/ruact/routing.rb
CHANGED
|
@@ -24,9 +24,9 @@ module Ruact
|
|
|
24
24
|
# The path segment reuses {Ruact::ServerFunctions::NameBridge} verbatim
|
|
25
25
|
# (D4): `def search_users` → `GET /q/searchUsers`, named
|
|
26
26
|
# `ruact_query_searchUsers`. Invalid or JS-reserved method names raise
|
|
27
|
-
# {Ruact::ConfigurationError} at route-draw time
|
|
28
|
-
# the same method name collide on the route
|
|
29
|
-
#
|
|
27
|
+
# {Ruact::ConfigurationError} at route-draw time, and so do two query
|
|
28
|
+
# classes mounting the same method name (they would collide on the route
|
|
29
|
+
# name and on the export) — both are loud boot failures, never request-time
|
|
30
30
|
# surprises.
|
|
31
31
|
#
|
|
32
32
|
# The generated dispatch controller PRESERVES the query class's namespace
|
|
@@ -56,11 +56,29 @@ module Ruact
|
|
|
56
56
|
|
|
57
57
|
query_class.public_instance_methods(false).each do |query_method|
|
|
58
58
|
js_identifier = ServerFunctions::NameBridge.to_js_identifier(query_method)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
begin
|
|
60
|
+
mapper.get("#{prefix}/#{js_identifier}",
|
|
61
|
+
to: "#{target}##{query_method}",
|
|
62
|
+
as: :"ruact_query_#{js_identifier}")
|
|
63
|
+
rescue ArgumentError => e
|
|
64
|
+
raise unless e.message.include?("already in use")
|
|
65
|
+
|
|
66
|
+
raise_query_name_taken!(query_class, query_method, "#{prefix}/#{js_identifier}")
|
|
67
|
+
end
|
|
62
68
|
end
|
|
63
69
|
end
|
|
70
|
+
|
|
71
|
+
# Rails' own message ("Invalid route name, already in use") names a route
|
|
72
|
+
# the app never wrote. Name the query, the method and the way out.
|
|
73
|
+
def raise_query_name_taken!(query_class, query_method, path)
|
|
74
|
+
raise Ruact::ConfigurationError,
|
|
75
|
+
"#{query_class}##{query_method} cannot be mounted: GET #{path} is already mounted " \
|
|
76
|
+
"in these routes — by another query class that also defines `#{query_method}`, or " \
|
|
77
|
+
"by `ruact_queries #{query_class}` appearing twice. Query names share one namespace " \
|
|
78
|
+
"(one route and one export of @/.ruact/server-functions per name): remove the " \
|
|
79
|
+
"duplicate mount, or rename one of the methods (the scaffold names its search " \
|
|
80
|
+
"after the resource: search_posts, search_comments)."
|
|
81
|
+
end
|
|
64
82
|
end
|
|
65
83
|
end
|
|
66
84
|
end
|
data/lib/ruact/server.rb
CHANGED
|
@@ -277,12 +277,21 @@ module Ruact
|
|
|
277
277
|
_ensure_url_is_http_header_safe(location)
|
|
278
278
|
location = _enforce_open_redirect_protection(location, allow_other_host: allow_other_host)
|
|
279
279
|
|
|
280
|
+
# Story 13.3 (FR98) — registered errors ride flash to the page the
|
|
281
|
+
# runtime navigates to (a router Flight GET: no layout prints them). A
|
|
282
|
+
# function call never reaches Ruact::Controller#redirect_to's Flight path,
|
|
283
|
+
# so the stash lives here too (Story 17.0g review R3). Same-origin only,
|
|
284
|
+
# like that path: after a redirect to another origin the flash entry
|
|
285
|
+
# would wait in the session for whatever page the user opens next.
|
|
286
|
+
path = __ruact_redirect_path(location)
|
|
287
|
+
__ruact_stash_errors_in_flash if path.start_with?("/") && !path.start_with?("//")
|
|
288
|
+
|
|
280
289
|
# Story 15.0 (F6) — a Bucket-2 `redirect_to` is a ruact-owned response
|
|
281
290
|
# (`$redirect`; registered errors ride flash), not an injection opt-out.
|
|
282
291
|
@__ruact_function_response_owned = true
|
|
283
292
|
# Story 15.5 (FR109) — mark the `$redirect` sub-shape for the dev log.
|
|
284
293
|
@__ruact_function_redirect = true
|
|
285
|
-
render json: { "$redirect" =>
|
|
294
|
+
render json: { "$redirect" => path }
|
|
286
295
|
end
|
|
287
296
|
|
|
288
297
|
private
|
data/lib/ruact/version.rb
CHANGED
data/lib/ruact/view_helper.rb
CHANGED
|
@@ -25,12 +25,71 @@ module Ruact
|
|
|
25
25
|
# placeholder in the HTML output.
|
|
26
26
|
def __ruact_component__(name, props = {})
|
|
27
27
|
ctx = @ruact_render_context
|
|
28
|
-
raise Ruact::Error,
|
|
28
|
+
raise Ruact::Error, __ruact_outside_render_message(name) if ctx.nil?
|
|
29
29
|
|
|
30
30
|
token = ctx.register(name, props)
|
|
31
31
|
"<!-- #{token} -->".html_safe
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
+
# Story 17.0i — a client component in a template Rails is rendering on its
|
|
35
|
+
# own. Says which component and template, and the two ways to a ruact page:
|
|
36
|
+
# `ruact_render` for this template, or listing the action in the
|
|
37
|
+
# controller's `ruact_pages`. The file and line come from the
|
|
38
|
+
# `ActionView::Template::Error` Rails wraps this in.
|
|
39
|
+
def __ruact_outside_render_message(name)
|
|
40
|
+
template = @current_template&.virtual_path
|
|
41
|
+
where = template ? "\"#{template}\"" : "this template"
|
|
42
|
+
# The helper is in every view. A controller action without the concern
|
|
43
|
+
# has no `ruact_render` to call; a mailer (its view's `controller`) or a
|
|
44
|
+
# `Controller.render` that ran no action cannot render through ruact at all.
|
|
45
|
+
owner = respond_to?(:controller) ? controller : nil
|
|
46
|
+
declined = owner&.instance_variable_get(:@__ruact_declined_render_options)
|
|
47
|
+
return __ruact_declined_options_message(name, where, declined) if declined.present?
|
|
48
|
+
|
|
49
|
+
if owner && !(defined?(Ruact::Controller) && owner.class.include?(Ruact::Controller) &&
|
|
50
|
+
__ruact_page_request?(owner))
|
|
51
|
+
return __ruact_not_a_page_message(name, where) unless __ruact_page_request?(owner)
|
|
52
|
+
|
|
53
|
+
return "ruact: <#{name} /> is a client component, and #{where} is being rendered by " \
|
|
54
|
+
"#{owner.class.name}, which does not include Ruact::Controller. Add " \
|
|
55
|
+
"`include Ruact::Controller` to it: its pages then render through ruact."
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# A partial or a layout is not a page: rendering it as one is not the fix.
|
|
59
|
+
call = if template.nil? || File.basename(template).start_with?("_") || template.start_with?("layouts/")
|
|
60
|
+
"`ruact_render(template: …, status: …)` for the page that renders it"
|
|
61
|
+
else
|
|
62
|
+
"`ruact_render(template: \"#{template}\", status: …)`"
|
|
63
|
+
end
|
|
64
|
+
"ruact: <#{name} /> is a client component, and #{where} is being rendered by Rails, outside ruact. " \
|
|
65
|
+
"Render it through ruact: #{call} — or, when it is this controller's page and the controller " \
|
|
66
|
+
"declares `ruact_pages`, add the action there (`render :action` then goes through ruact)."
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# A controller answering a request with an action — not a mailer, not an
|
|
70
|
+
# `ActionController::Renderer` (no action ran).
|
|
71
|
+
def __ruact_page_request?(owner)
|
|
72
|
+
defined?(ActionController::Metal) && owner.is_a?(ActionController::Metal) && owner.action_name.present?
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Story 17.0i review R3 — the page was ruact's, but `render` carried an
|
|
76
|
+
# option ruact does not take, so Rails rendered it.
|
|
77
|
+
def __ruact_declined_options_message(name, where, options)
|
|
78
|
+
named = options.map { |option| "`#{option}:`" }.join(", ")
|
|
79
|
+
"ruact: <#{name} /> is a client component, and #{where} is a ruact page, but its `render` got " \
|
|
80
|
+
"#{named} — not a render option ruact takes — so Rails rendered it outside ruact. Remove it " \
|
|
81
|
+
"(a flash message goes in `flash.now`), or render the page with `ruact_render`."
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def __ruact_not_a_page_message(name, where)
|
|
85
|
+
"ruact: <#{name} /> is a client component, and #{where} is being rendered outside a request to a " \
|
|
86
|
+
"ruact controller (a mailer, or `Controller.render`). Client components render only in a page a " \
|
|
87
|
+
"ruact controller renders."
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private :__ruact_outside_render_message, :__ruact_page_request?, :__ruact_not_a_page_message,
|
|
91
|
+
:__ruact_declined_options_message
|
|
92
|
+
|
|
34
93
|
# Story 14.2 (FR104) — emits ruact's full JavaScript asset block: the
|
|
35
94
|
# dev/prod bootstrap entry `<script>` tags (re-targeting the virtual entry
|
|
36
95
|
# `virtual:ruact/bootstrap` — in dev the react-refresh preamble + `@vite/client`
|
|
@@ -65,8 +124,94 @@ module Ruact
|
|
|
65
124
|
parts.join("\n").html_safe
|
|
66
125
|
end
|
|
67
126
|
|
|
127
|
+
# The `<head>` half of the asset contract: the stylesheets Vite emitted for
|
|
128
|
+
# the client components, linked so they reach the page in production.
|
|
129
|
+
#
|
|
130
|
+
# Vite is an ASSET bundler, not a JS bundler — a `"use client"` component
|
|
131
|
+
# that imports CSS (its own, or one a package ships) produces a stylesheet
|
|
132
|
+
# recorded on the manifest entry beside `file`. Nothing linked it, so the
|
|
133
|
+
# styling was built, digest-stamped, served and never referenced. Only
|
|
134
|
+
# production was affected: the dev server injects that CSS through JS.
|
|
135
|
+
#
|
|
136
|
+
# **Why this is separate from {#ruact_js_assets}, and why it belongs in
|
|
137
|
+
# `<head>`.** The JS helper is injected before `</body>`, and the built-in
|
|
138
|
+
# shell emits it there too — a position that did not matter while it emitted
|
|
139
|
+
# only a `<script>`. A stylesheet there is discovered late and sits AFTER
|
|
140
|
+
# when the browser finds it, and it means third-party CSS outranks the app's
|
|
141
|
+
# own. So ruact declares that it contributes CSS and says WHERE, rather than
|
|
142
|
+
# smuggling it through a helper named for JavaScript.
|
|
143
|
+
#
|
|
144
|
+
# **Cascade.** Call it ABOVE the app's `stylesheet_link_tag`, so the app's
|
|
145
|
+
# own CSS is loaded afterwards. The layout ruact ships (`layouts/ruact`,
|
|
146
|
+
# Story 17.0b) does exactly that; a layout of the app's own does it by hand
|
|
147
|
+
# (`rails generate ruact:install` prints the line, it never edits a layout).
|
|
148
|
+
# Order decides ties only — specificity, `!important` and cascade layers all
|
|
149
|
+
# outrank it — but ties are the common case, and losing them by default is
|
|
150
|
+
# what makes third-party CSS feel like it "takes over".
|
|
151
|
+
#
|
|
152
|
+
# **With the dev server reachable it links no stylesheet**, deliberately:
|
|
153
|
+
# Vite is already injecting the CSS, and linking the file on disk would serve
|
|
154
|
+
# whatever the last build left there. In development WITHOUT the dev server it
|
|
155
|
+
# falls back to the built manifest, matching what `ruact_vite_tags` does.
|
|
156
|
+
#
|
|
157
|
+
# It reads the SAME manifest entry as {#ruact_js_assets}, in the same render,
|
|
158
|
+
# so the script and the stylesheet can never come from different builds.
|
|
159
|
+
#
|
|
160
|
+
# **In a document ruact renders it also emits
|
|
161
|
+
# `<meta name="turbo-visit-control" content="reload">`** (Story 17.0f), in
|
|
162
|
+
# every environment: Turbo Drive then loads a ruact page in full instead of
|
|
163
|
+
# swapping it into its own document. Outside a ruact render — the same layout
|
|
164
|
+
# rendering a plain Rails page — it does not.
|
|
165
|
+
#
|
|
166
|
+
# @return [ActiveSupport::SafeBuffer] the meta (in a ruact document) followed
|
|
167
|
+
# by the `<link>` markup, html_safe; no links in development with Vite
|
|
168
|
+
# running, when no entry exists, or when the entry declares no CSS
|
|
169
|
+
# @example In a layout
|
|
170
|
+
# <head>
|
|
171
|
+
# <%= ruact_head_assets %>
|
|
172
|
+
# <%= stylesheet_link_tag :app %>
|
|
173
|
+
# </head>
|
|
174
|
+
def ruact_head_assets
|
|
175
|
+
tags = []
|
|
176
|
+
tags.push(TURBO_VISIT_CONTROL, TURBO_PREFETCH) if ruact_document?
|
|
177
|
+
tags.concat(ruact_component_stylesheets) unless Rails.env.development? && vite_dev_running?
|
|
178
|
+
tags.join("\n").html_safe
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Story 17.0f (FR117) — a document ruact rendered must not be swapped into
|
|
182
|
+
# the page by Turbo Drive: Turbo would keep ITS document, the ruact bootstrap
|
|
183
|
+
# (a module script, evaluated once per document) would not run again, and
|
|
184
|
+
# the two routers would end up fighting over one page — dead links and
|
|
185
|
+
# blank pages after a single round trip (spike 2026-09-12, S3–S7). With this
|
|
186
|
+
# meta Turbo does a full load instead. It does not depend on Vite, so it is
|
|
187
|
+
# emitted in development too.
|
|
188
|
+
TURBO_VISIT_CONTROL = %(<meta name="turbo-visit-control" content="reload">)
|
|
189
|
+
|
|
190
|
+
# Story 17.0f (decided by Luiz, 2026-09-25) — in a document ruact rendered,
|
|
191
|
+
# the ruact router owns every click, but a layout that also loads Turbo 8
|
|
192
|
+
# still lets Turbo PREFETCH links on hover: a GET that runs the destination's
|
|
193
|
+
# action for a click Turbo will never handle (seen in playgrounds/
|
|
194
|
+
# nav-islands). Off in ruact documents; untouched everywhere else.
|
|
195
|
+
TURBO_PREFETCH = %(<meta name="turbo-prefetch" content="false">)
|
|
196
|
+
|
|
68
197
|
private
|
|
69
198
|
|
|
199
|
+
# Whether the document being rendered is ruact's. `render_ruact_document`
|
|
200
|
+
# sets `@ruact_flight_payload` for the whole render (copied into the view by
|
|
201
|
+
# Rails), and removes it after. The same layout rendering a plain Rails page
|
|
202
|
+
# — the app's layout, in whole-app mode — does not have it, and must not
|
|
203
|
+
# tell Turbo to reload every visit.
|
|
204
|
+
def ruact_document?
|
|
205
|
+
instance_variable_defined?(:@ruact_flight_payload) && !@ruact_flight_payload.nil?
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def ruact_component_stylesheets
|
|
209
|
+
entry = vite_manifest_entry(Ruact.bootstrap_virtual_id)
|
|
210
|
+
return [] if entry.nil?
|
|
211
|
+
|
|
212
|
+
Array(entry["css"]).map { |file| %(<link rel="stylesheet" href="/assets/#{file}">) }
|
|
213
|
+
end
|
|
214
|
+
|
|
70
215
|
# The `__FLIGHT_DATA` inline bootstrap `<script>` — pushes the per-render
|
|
71
216
|
# Flight payload onto the global queue the bootstrap entry drains on boot.
|
|
72
217
|
# `</script>` in the payload is escaped to prevent an HTML/XSS breakout
|
|
@@ -139,7 +284,19 @@ module Ruact
|
|
|
139
284
|
false
|
|
140
285
|
end
|
|
141
286
|
|
|
287
|
+
# Memoized FOR THE DURATION OF ONE RENDER, which is what lets
|
|
288
|
+
# `ruact_head_assets` and `ruact_js_assets` promise they describe the same
|
|
289
|
+
# build. They are separate calls in the template, so without this a deploy
|
|
290
|
+
# landing between them serves one build's stylesheet beside another build's
|
|
291
|
+
# script — verified reachable in review, not hypothetical.
|
|
142
292
|
def vite_manifest_entry(src_path)
|
|
293
|
+
@__ruact_manifest_entries ||= {}
|
|
294
|
+
return @__ruact_manifest_entries[src_path] if @__ruact_manifest_entries.key?(src_path)
|
|
295
|
+
|
|
296
|
+
@__ruact_manifest_entries[src_path] = read_vite_manifest_entry(src_path)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def read_vite_manifest_entry(src_path)
|
|
143
300
|
manifest_path = Rails.root.join("public", "assets", ".vite", "manifest.json")
|
|
144
301
|
return nil unless File.exist?(manifest_path)
|
|
145
302
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<%# The document a ruact page renders into when `config.layout = "ruact"` (what
|
|
2
|
+
`rails generate ruact:install` writes). It ships with the gem and is found by
|
|
3
|
+
name through a view path the Railtie appends, behind the app's — so an
|
|
4
|
+
`app/views/layouts/ruact.html.erb` wins over it. `rails generate ruact:layout`
|
|
5
|
+
puts a copy there; once copied, that file is the app's and no longer changes
|
|
6
|
+
when the gem does.
|
|
7
|
+
|
|
8
|
+
What reaches the <head>: CSRF and CSP, the stylesheets Vite built for the
|
|
9
|
+
client components, then the app's stylesheets named in
|
|
10
|
+
`config.layout_stylesheets` — in that order, so the app's CSS loads last and
|
|
11
|
+
wins ties. What does NOT: the app's JavaScript (importmap, Turbo, Stimulus)
|
|
12
|
+
and anything else its own layout adds. The page that rendered the document
|
|
13
|
+
owns its navigation; to bring more of the app in, copy this file.
|
|
14
|
+
|
|
15
|
+
The <title> is the app's name on every page: a ruact view renders in its own
|
|
16
|
+
pass, so nothing it sets can reach this layout. A copied layout can title
|
|
17
|
+
pages however it likes. %>
|
|
18
|
+
<!DOCTYPE html>
|
|
19
|
+
<html>
|
|
20
|
+
<head>
|
|
21
|
+
<title><%= (Rails.application.class.module_parent_name || Rails.application.class.name).to_s.titleize %></title>
|
|
22
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
23
|
+
<%= csrf_meta_tags %>
|
|
24
|
+
<%= csp_meta_tag %>
|
|
25
|
+
<%= ruact_head_assets %>
|
|
26
|
+
<%= stylesheet_link_tag(*Ruact.config.layout_stylesheets) if Ruact.config.layout_stylesheets.any? %>
|
|
27
|
+
</head>
|
|
28
|
+
<body>
|
|
29
|
+
<div id="root"></div>
|
|
30
|
+
<%= ruact_js_assets %>
|
|
31
|
+
</body>
|
|
32
|
+
</html>
|
data/lib/ruact.rb
CHANGED
|
@@ -19,10 +19,25 @@ require_relative "ruact/view_helper"
|
|
|
19
19
|
require_relative "ruact/erb_preprocessor_hook"
|
|
20
20
|
require_relative "ruact/server_functions"
|
|
21
21
|
require_relative "ruact/query"
|
|
22
|
+
# Story 17.0f — loaded here, not only by the Railtie initializer that installs
|
|
23
|
+
# it, so `config.middleware.delete Ruact::NavigationBoundary::Middleware` in
|
|
24
|
+
# config/application.rb can name the constant before initializers run.
|
|
25
|
+
require_relative "ruact/navigation_boundary"
|
|
22
26
|
# Railtie loads ruact/controller when inside a Rails app
|
|
23
27
|
require_relative "ruact/railtie" if defined?(Rails)
|
|
24
28
|
|
|
25
29
|
module Ruact
|
|
30
|
+
# Story 17.0b — the name of the layout the gem ships (lib/ruact/views/layouts/
|
|
31
|
+
# ruact.html.erb), and what `rails generate ruact:install` writes into
|
|
32
|
+
# `config.layout`.
|
|
33
|
+
GEM_LAYOUT = "ruact"
|
|
34
|
+
|
|
35
|
+
# Story 17.0g — a real `include` of Ruact::Controller in a Ruby source line,
|
|
36
|
+
# as `ruact:install` and `ruact:doctor` read it: `include Ruact::Controller`,
|
|
37
|
+
# `include(Ruact::Controller)`, `include ::Ruact::Controller`, or among
|
|
38
|
+
# others in one `include`. A mention in a comment does not count.
|
|
39
|
+
CONTROLLER_INCLUDE = /^[ \t]*include\b[^\n#]*?(?<![\w:])(?:::)?Ruact::Controller\b/
|
|
40
|
+
|
|
26
41
|
class << self
|
|
27
42
|
attr_accessor :manifest, :streaming_mode
|
|
28
43
|
|
|
@@ -46,6 +61,20 @@ module Ruact
|
|
|
46
61
|
File.expand_path("../vendor/javascript/vite-plugin-ruact/index.js", __dir__)
|
|
47
62
|
end
|
|
48
63
|
|
|
64
|
+
# Story 17.0b — the directory of views the gem ships: today, the
|
|
65
|
+
# `layouts/ruact` document a ruact page renders into when
|
|
66
|
+
# `config.layout = "ruact"`. The Railtie APPENDS it to the controller view
|
|
67
|
+
# paths, so it sits behind the app and every engine — an app's own
|
|
68
|
+
# `app/views/layouts/ruact.html.erb` wins — and `rails generate ruact:layout`
|
|
69
|
+
# copies from here when an app wants to own that file.
|
|
70
|
+
#
|
|
71
|
+
# Under `lib/` on purpose: `app/` is not packaged (see {Ruact::Packaging}).
|
|
72
|
+
#
|
|
73
|
+
# @return [String] absolute path to lib/ruact/views
|
|
74
|
+
def views_path
|
|
75
|
+
File.expand_path("ruact/views", __dir__)
|
|
76
|
+
end
|
|
77
|
+
|
|
49
78
|
# Story 14.2 (FR104) — the SINGLE source of truth for the bootstrap entry id.
|
|
50
79
|
# ruact's React entry is served as the virtual module `virtual:ruact/bootstrap`
|
|
51
80
|
# (by the bundled Vite plugin) instead of a `app/javascript/application.jsx`
|