ruflet_rails 0.0.13 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dc30304299f6e6c539c8fcc838c7f843b54b2d106e6219c657ac8c856c69f9cf
4
- data.tar.gz: 52ec66b70428d3d1d4ff2d3d7ac4cf6bae34b497addd74e6ba39f8f9c3cc5d3f
3
+ metadata.gz: '097cfb74aac7af1e15b8bcb49135851595cbd7a1856eef1b4df39672c95ebab8'
4
+ data.tar.gz: 27d2e764c20535ebc41befd5672741500cb78387ee74f56ea284a60de9d8d721
5
5
  SHA512:
6
- metadata.gz: 976c713a63c7a6f3663fbd2541474008e010947a81c6d205cb43f318d6e6fbdaae59c71564f145f34311570518c1910ead0f0278173e270f9ce6bbda3bd4d659
7
- data.tar.gz: 88d2668bd895b3e817bae881f65ab8fa395ec54377b850a9f7aef163bcf1caa96982656103fdeb8b6034a40271ae1c4dfe80ae43a7f987fd0cf816e56f28cb2d
6
+ metadata.gz: ad4c0ab96a6741c9ff4339590e854abf2e72648a0b10c85bdf371ff7aaaee005e394f5063eecf9395756e1d9260d021a4d6c54737a8ad6326e811e63dca9d75a
7
+ data.tar.gz: c5aa7b3df32bedad4122d379739e05f770b8caa359e3ac01b114a29ee9c89bc7c5a3ecd175857dc79eaccb737b33bcd007d977086061b9ad25c9187872a2f994
data/README.md CHANGED
@@ -3,8 +3,8 @@
3
3
  `ruflet_rails` is the Rails-first integration package for Ruflet.
4
4
 
5
5
  It mounts Ruby-driven Ruflet interfaces in a Rails application, makes Rails
6
- models available to Ruflet components, and connects web, mobile, and desktop
7
- clients to the same application entrypoint.
6
+ views able to opt into native WebView chrome, and connects web, mobile, and
7
+ desktop clients to the same application entrypoint.
8
8
 
9
9
  ## Add The Gem
10
10
 
@@ -24,27 +24,29 @@ bin/rails generate ruflet:install --web --desktop
24
24
 
25
25
  This generator will:
26
26
  - create `app/views/ruflet/main.rb`
27
- - create `ruflet.yaml`
27
+ - create `config/initializers/ruflet.rb`
28
28
  - add the Ruflet WebSocket route to `config/routes.rb`
29
29
  - add a `/ruflet` web mount when `--web` is used
30
30
  - download prebuilt clients from GitHub releases when `--web`, `--desktop`, or
31
31
  `--client=web|desktop|all` is used
32
32
 
33
- Generated `ruflet.yaml`:
33
+ Generated `config/initializers/ruflet.rb`:
34
34
 
35
- ```yaml
36
- app:
37
- name: My App
38
- backend_url: http://localhost:3000
35
+ ```ruby
36
+ Ruflet::Rails.configure do |config|
37
+ config.app_name = "My App"
38
+ config.backend_url = ENV.fetch("RUFLET_BACKEND_URL", "http://localhost:3000")
39
39
 
40
- services: []
40
+ config.services = []
41
41
 
42
- assets:
43
- splash_screen: assets/splash.png
44
- icon_launcher: assets/icon.png
42
+ config.splash_screen = Rails.root.join("app/assets/images/splash.png")
43
+ config.icon_launcher = Rails.root.join("app/assets/images/icon.png")
44
+ end
45
45
  ```
46
46
 
47
- For Rails apps, those asset paths are resolved from `app/assets/` during build.
47
+ At build time `ruflet_rails` serializes this Rails config into the Ruflet CLI
48
+ config shape, so the initializer remains the source of truth for app name,
49
+ backend URL, services, assets, and build colors.
48
50
 
49
51
  ## Web client
50
52
 
@@ -86,7 +88,7 @@ bundle exec rake ruflet:build[desktop]
86
88
  ```
87
89
 
88
90
  Rails desktop builds are server-driven. The built desktop app connects back to the
89
- Rails backend configured in `ruflet.yaml`; it does not package a self-contained
91
+ Rails backend configured in `config/initializers/ruflet.rb`; it does not package a self-contained
90
92
  Ruby runtime.
91
93
 
92
94
  Plain Rails dev server commands do not launch the desktop app. Request a desktop
@@ -118,82 +120,183 @@ bundle exec rake ruflet:install
118
120
  bundle exec rake ruflet:install[DEVICE_ID]
119
121
  ```
120
122
 
121
- ## Ruflet resource scaffolds
123
+ ## Native WebView shell
122
124
 
123
- The standard Rails scaffold command also generates a mountable Ruflet CRUD
124
- component:
125
+ Beyond the server-driven UI, Ruflet can wrap an existing Rails HTML app in a
126
+ managed native WebView. This is an opt-in shell: calling
127
+ `Ruflet::Rails.native_app` wraps web pages in a native Ruflet shell whose body is
128
+ a WebView. Plain `Ruflet.run { |page| ... }` remains a normal Ruflet app with no
129
+ WebView wrapper or HTML adapter.
125
130
 
126
- ```bash
127
- bin/rails generate scaffold Post title:string body:text published:boolean
128
- ```
129
-
130
- The Rails model attributes are passed to the Ruflet component generator. The
131
- scaffold creates generated application code the Rails developer can own and
132
- edit:
131
+ Native behavior is explicit from Rails views: ordinary links and Turbo visits
132
+ stay inside the WebView, while links annotated with `data-ruflet-*` can push
133
+ native screens, replace/root the stack, open a sheet, show a dialog, toast, or
134
+ promote page chrome.
133
135
 
134
136
  ```ruby
135
- # app/views/ruflet/components/posts/post_component.rb
136
- class PostComponent < Ruflet::Rails::ResourceComponent
137
- def render
138
- # edit the scaffold layout here
139
- end
137
+ # app/views/ruflet/main.rb
138
+ Ruflet.run do |page|
139
+ Ruflet::Rails.native_app(
140
+ page,
141
+ start_url: "https://myapp.com",
142
+ title: "My App", # opt into a native AppBar (tracks <title>)
143
+ loading: :shimmer # default; can be "Loading..." or false
144
+ )
140
145
  end
141
146
  ```
142
147
 
143
- Mount it explicitly in `config/routes.rb`:
148
+ ### Crossing web and native from HTML
144
149
 
145
- ```ruby
146
- mount Ruflet::Rails.web_app(view: "PostComponent"), at: "/posts"
150
+ A tiny HTML adapter reads `data-ruflet-*` attributes from the rendered page and
151
+ sends those payloads back to Ruby. Ruby then builds the native AppBar, drawer,
152
+ tabs, dialogs, sheets, services, and navigation with normal Ruflet controls. The
153
+ page stays a normal page in a plain browser; the attributes only activate inside
154
+ the native shell. Keep using normal Rails helpers like `link_to`; add Ruflet data
155
+ attributes only where the native app should augment the web behavior.
156
+
157
+ **Navigation** — `action` is `push` (default), `root` (reset to a root screen,
158
+ tab-style), `replace`, `sheet`, or `back`:
159
+
160
+ ```erb
161
+ <%= link_to "Messages", messages_path,
162
+ data: { ruflet_screen: { action: "push", title: "Messages" }.to_json } %>
163
+
164
+ <%= link_to "Sign in", new_session_path,
165
+ data: { ruflet_screen: { action: "push", title: "Sign in",
166
+ leading: { icon: "close", action: "back" } }.to_json } %>
167
+
168
+ <%= link_to "Sign in", new_session_path,
169
+ data: { ruflet_screen: { action: "sheet" }.to_json } %>
170
+
171
+ <a href="/dashboard" data-ruflet-screen='{"action":"root","title":"Dashboard"}'>Dashboard</a>
172
+ ```
173
+
174
+ **Promote HTML chrome to native** — hide HTML header/nav elements and render
175
+ native AppBar, NavigationBar, NavigationDrawer, or desktop NavigationRail:
176
+
177
+ ```erb
178
+ <%= tag.div hidden: true,
179
+ data: { ruflet_appbar: { title: "Sign in",
180
+ leading: { icon: "close", action: "back" } }.to_json } %>
181
+
182
+ <%= ruflet_appbar "Inbox", leading: { icon: "menu", action: "drawer" } do %>
183
+ <%= ruflet_appbar_action "search", search_path %>
184
+ <% end %>
185
+
186
+ <%= ruflet_drawer do %>
187
+ <%= ruflet_drawer_item "Home", root_path, icon: "home", selected: true %>
188
+ <%= ruflet_drawer_item "Settings", settings_path, icon: "settings", nav: :push %>
189
+ <% end %>
190
+
191
+ <%= ruflet_bottom_nav do %>
192
+ <%= ruflet_nav_item "Home", root_path, icon: "house", selected: true %>
193
+ <%= ruflet_nav_item "Profile", profile_path, icon: "person" %>
194
+ <% end %>
195
+
196
+ <%= ruflet_navigation_rail extended: true, breakpoint: 720 do %>
197
+ <%= ruflet_rail_item "Home", root_path, icon: "home", selected: true %>
198
+ <%= ruflet_rail_item "Inbox", inbox_path, icon: "mail" %>
199
+ <%= ruflet_rail_item "Settings", settings_path, icon: "settings", nav: :push %>
200
+ <% end %>
147
201
  ```
148
202
 
149
- The generated component contains the developer-owned UI and persistence calls.
150
- `ruflet_rails` provides the reusable model, navigation, dialog, and formatting
151
- helpers. Component files under `app/views/ruflet/components` are loaded by the
152
- Railtie so both web mounts and `main.rb` can reference them.
203
+ **Native dialogs and toasts** from annotated Rails links and buttons.
204
+ Dialogs, bottom sheets, and snackbars are adaptive by default, so the native shell
205
+ can use platform-appropriate presentation instead of forcing the same Material
206
+ look everywhere:
153
207
 
154
- Use `--skip-ruflet` when a Rails scaffold should not generate a Ruflet
155
- component.
208
+ ```erb
209
+ <%= link_to "Delete", item_path(item),
210
+ data: { ruflet_action: { component: "dialog", title: "Delete?",
211
+ content: "This cannot be undone.", confirm: "Delete", action: "replace" }.to_json } %>
156
212
 
157
- ## Ruflet model forms
213
+ <%= button_tag "Copy link",
214
+ data: { ruflet_action: { component: "toast", message: "Copied to clipboard" }.to_json } %>
215
+ ```
158
216
 
159
- Generate only a reusable Ruflet form for an existing Rails model:
217
+ **Native menus and bottom sheets** menus are regular Ruflet bottom sheets
218
+ driven by Rails payload data. Item taps close the native sheet first, wait for
219
+ the native dismiss event, then run the item callback or navigation. This keeps
220
+ the overlay lifecycle stable on mobile and desktop.
221
+
222
+ ```erb
223
+ <%= ruflet_appbar "T4U",
224
+ actions: [
225
+ {
226
+ icon: "language",
227
+ action: "menu",
228
+ title: "Language",
229
+ items: [
230
+ { label: "FR", icon: "check", url: url_for(locale: :fr), action: "root", selected: I18n.locale == :fr },
231
+ { label: "EN", icon: "translate", url: url_for(locale: :en), action: "root" },
232
+ { label: "AR", icon: "translate", url: url_for(locale: :ar), action: "root" }
233
+ ]
234
+ }
235
+ ] %>
236
+ ```
160
237
 
161
- ```bash
162
- bin/rails generate ruflet:form Post
238
+ Items close the sheet by default. Pass `close: false` only for an item that
239
+ should run without dismissing the sheet:
240
+
241
+ ```erb
242
+ <%= ruflet_appbar "Filters",
243
+ actions: [
244
+ {
245
+ icon: "tune",
246
+ action: "menu",
247
+ title: "Filters",
248
+ items: [
249
+ { label: "Toggle remote only", icon: "check", close: false }
250
+ ]
251
+ }
252
+ ] %>
163
253
  ```
164
254
 
165
- When no fields are passed, the generator reads the model columns and skips `id`,
166
- `created_at`, and `updated_at`. You can also pass fields explicitly:
255
+ Use `action: "sheet"` to present a Rails URL inside a native bottom sheet whose
256
+ body is still a WebView:
167
257
 
168
- ```bash
169
- bin/rails generate ruflet:form Post title:string body:text published:boolean category:references
258
+ ```erb
259
+ <%= link_to "Choose language", languages_path,
260
+ data: { ruflet_action: { component: "sheet", url: languages_path }.to_json } %>
261
+ ```
262
+
263
+ Inside a WebView sheet, plain Rails links are promoted to native actions so the
264
+ sheet closes before navigation:
265
+
266
+ ```erb
267
+ <!-- app/views/languages/index.html.erb, rendered inside the sheet -->
268
+ <%= link_to "Français", url_for(locale: :fr) %>
269
+ <%= link_to "English", url_for(locale: :en) %>
270
+ <%= link_to "العربية", url_for(locale: :ar) %>
170
271
  ```
171
272
 
172
- Foreign keys and references, such as `category:references` or `user_id`, render
173
- as Ruflet dropdowns populated from the associated Rails model.
273
+ Add `data-ruflet-close="false"` when a sheet link should not dismiss:
174
274
 
175
- The generated form lives at `app/views/ruflet/components/posts/post_form.rb`.
176
- The form generator creates `app/views/ruflet/components/application_component.rb`
177
- when that base component does not already exist.
275
+ ```erb
276
+ <%= link_to "Preview", preview_path, data: { ruflet_close: "false" } %>
277
+ ```
178
278
 
179
- ## Shared Ruflet components
279
+ **Native services** ERB can trigger safe platform services through the same
280
+ `data-ruflet-action` channel:
180
281
 
181
- Put shared Ruflet UI components under `app/views/ruflet/components`. Those files
182
- are reloaded with Rails application code:
282
+ ```erb
283
+ <%= ruflet_share_link "Share", "#",
284
+ text: "Look at this", title: "My App" %>
183
285
 
184
- ```ruby
185
- # app/views/ruflet/components/page_title_component.rb
186
- class PageTitleComponent < ApplicationComponent
187
- def render(value)
188
- text(value, size: desktop? || web? ? 28 : 24, weight: "bold")
189
- end
190
- end
286
+ <%= ruflet_copy_button "Copy invite", text: invite_url(@invite) %>
287
+
288
+ <%= ruflet_launch_link "Open docs", "https://flutteronrails.com" %>
289
+
290
+ <%= ruflet_haptic_button "Tap", style: "light" %>
191
291
  ```
192
292
 
293
+ For a Fizzy-style web app, keep `Ruflet::Rails.native_app` simple and opt in from
294
+ the views that need native treatment:
295
+
193
296
  ```ruby
194
297
  # app/views/ruflet/main.rb
195
298
  Ruflet.run do |page|
196
- page.add(PageTitleComponent.render(page, "Posts"))
299
+ Ruflet::Rails.native_app(page, start_url: "#{Ruflet::Rails.backend_url}/")
197
300
  end
198
301
  ```
199
302
 
@@ -19,11 +19,11 @@ module Ruflet
19
19
  create_file target, Ruflet::Rails::InstallSupport.default_app_template(app_title: app_name)
20
20
  end
21
21
 
22
- def create_ruflet_yaml
23
- target = File.join(destination_root, "ruflet.yaml")
22
+ def create_ruflet_initializer
23
+ target = File.join(destination_root, "config", "initializers", "ruflet.rb")
24
24
  return if File.exist?(target)
25
25
 
26
- create_file target, Ruflet::Rails::InstallSupport.default_ruflet_yaml(app_name: app_name)
26
+ create_file target, Ruflet::Rails::InstallSupport.default_initializer(app_name: app_name)
27
27
  end
28
28
 
29
29
  # Mount the native WebSocket endpoint explicitly in config/routes.rb.
@@ -4,9 +4,9 @@ module Ruflet
4
4
  module Rails
5
5
  # Central configuration for ruflet_rails.
6
6
  #
7
- # An install needs no config/initializers/ruflet.rb: routes mount
8
- # app/views/ruflet/main.rb explicitly and build metadata is read from
9
- # ruflet.yaml. Add an initializer only to override these settings:
7
+ # Rails apps keep Ruflet build metadata in config/initializers/ruflet.rb.
8
+ # The build task serializes this object to the ruflet.yaml structure the
9
+ # Ruflet CLI consumes, so Rails remains the source of truth:
10
10
  #
11
11
  # Ruflet::Rails.configure do |config|
12
12
  # # Runtime / server
@@ -15,8 +15,9 @@ module Ruflet
15
15
  # # App metadata (ruflet.yaml → app:)
16
16
  # config.app_name = "My App"
17
17
  #
18
- # # Services (ruflet.yaml → services:)
18
+ # # Services / extensions (ruflet.yaml → services: / extensions:)
19
19
  # config.services = []
20
+ # config.extensions = %w[webview]
20
21
  #
21
22
  # # Assets (ruflet.yaml → assets:)
22
23
  # config.splash_screen = Rails.root.join("app/assets/images/splash.png")
@@ -38,16 +39,17 @@ module Ruflet
38
39
  # --- Runtime / server ---
39
40
 
40
41
  # Backend base URL. Used as --dart-define=RUFLET_URL at build time
41
- # and by the desktop launcher. Replaces ruflet.yaml → app.backend_url.
42
+ # and by the desktop launcher.
42
43
  attr_accessor :backend_url
43
44
 
44
45
  # --- App metadata (ruflet.yaml → app:) ---
45
46
 
46
47
  attr_accessor :app_name
47
48
 
48
- # --- Services (ruflet.yaml → services:) ---
49
+ # --- Services / extensions (ruflet.yaml → services: / extensions:) ---
49
50
 
50
51
  attr_accessor :services
52
+ attr_accessor :extensions
51
53
 
52
54
  # --- Assets (ruflet.yaml → assets:) ---
53
55
 
@@ -71,6 +73,7 @@ module Ruflet
71
73
  @backend_url = nil
72
74
  @app_name = nil
73
75
  @services = []
76
+ @extensions = []
74
77
  end
75
78
 
76
79
  # Serialises config to the ruflet.yaml hash structure so the CLI can
@@ -84,7 +87,10 @@ module Ruflet
84
87
  app["backend_url"] = @backend_url if @backend_url
85
88
  hash["app"] = app unless app.empty?
86
89
 
87
- hash["services"] = Array(@services)
90
+ services = Array(@services)
91
+ extensions = Array(@extensions)
92
+ hash["services"] = services unless services.empty?
93
+ hash["extensions"] = extensions unless extensions.empty?
88
94
 
89
95
  assets = {}
90
96
  assets["splash_screen"] = @splash_screen.to_s if @splash_screen