ruflet_rails 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d142d63121dae012354a763ef756504d64534adbc4b2c11eaf0dc51b514c6b32
4
- data.tar.gz: d3d1e6cbf9af94c81d5514b71325484e547b40ba34afe3d8601a111f08e4af6a
3
+ metadata.gz: '097cfb74aac7af1e15b8bcb49135851595cbd7a1856eef1b4df39672c95ebab8'
4
+ data.tar.gz: 27d2e764c20535ebc41befd5672741500cb78387ee74f56ea284a60de9d8d721
5
5
  SHA512:
6
- metadata.gz: daf5bf66b258ece12da18cbb4c455d2f9f5dd141faa3638c2ef0ec9a64d85be9b25d94add15ae0374a96c6694f6e393ee015290df62368bd1d9cd6920a3185f3
7
- data.tar.gz: 075def7cfb22688619559ba1ca55ede0d4c152c403c95a2273c1fa322a2862b20aafe7bda0507bf73bb4a8e8f40e03eb6c87b9505900fb31aa3ac08098dbf88b
6
+ metadata.gz: ad4c0ab96a6741c9ff4339590e854abf2e72648a0b10c85bdf371ff7aaaee005e394f5063eecf9395756e1d9260d021a4d6c54737a8ad6326e811e63dca9d75a
7
+ data.tar.gz: c5aa7b3df32bedad4122d379739e05f770b8caa359e3ac01b114a29ee9c89bc7c5a3ecd175857dc79eaccb737b33bcd007d977086061b9ad25c9187872a2f994
data/README.md CHANGED
@@ -2,17 +2,18 @@
2
2
 
3
3
  `ruflet_rails` is the Rails-first integration package for Ruflet.
4
4
 
5
- Internal Rails transport/protocol code is bundled inside this gem as `Ruflet::Rails::Protocol`.
6
- No separate protocol gem is required.
5
+ It mounts Ruby-driven Ruflet interfaces in a Rails application, makes Rails
6
+ views able to opt into native WebView chrome, and connects web, mobile, and
7
+ desktop clients to the same application entrypoint.
7
8
 
8
- ## Usage
9
+ ## Add The Gem
9
10
 
10
11
  ```ruby
11
12
  # Gemfile
12
- gem "ruflet_rails", ">= 0.0.5"
13
+ gem "ruflet_rails"
13
14
  ```
14
15
 
15
- ## Install into Rails
16
+ ## Install Into Rails
16
17
 
17
18
  ```bash
18
19
  bin/rails generate ruflet:install
@@ -23,27 +24,29 @@ bin/rails generate ruflet:install --web --desktop
23
24
 
24
25
  This generator will:
25
26
  - create `app/views/ruflet/main.rb`
26
- - create `ruflet.yaml`
27
+ - create `config/initializers/ruflet.rb`
27
28
  - add the Ruflet WebSocket route to `config/routes.rb`
28
29
  - add a `/ruflet` web mount when `--web` is used
29
30
  - download prebuilt clients from GitHub releases when `--web`, `--desktop`, or
30
31
  `--client=web|desktop|all` is used
31
32
 
32
- Generated `ruflet.yaml`:
33
+ Generated `config/initializers/ruflet.rb`:
33
34
 
34
- ```yaml
35
- app:
36
- name: My App
37
- 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")
38
39
 
39
- services: []
40
+ config.services = []
40
41
 
41
- assets:
42
- splash_screen: assets/splash.png
43
- 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
44
45
  ```
45
46
 
46
- 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.
47
50
 
48
51
  ## Web client
49
52
 
@@ -85,7 +88,7 @@ bundle exec rake ruflet:build[desktop]
85
88
  ```
86
89
 
87
90
  Rails desktop builds are server-driven. The built desktop app connects back to the
88
- 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
89
92
  Ruby runtime.
90
93
 
91
94
  Plain Rails dev server commands do not launch the desktop app. Request a desktop
@@ -117,76 +120,183 @@ bundle exec rake ruflet:install
117
120
  bundle exec rake ruflet:install[DEVICE_ID]
118
121
  ```
119
122
 
120
- ## Ruflet resource scaffolds
123
+ ## Native WebView shell
121
124
 
122
- Generate a mountable Ruflet CRUD component for an existing Rails model:
123
-
124
- ```bash
125
- bin/rails generate ruflet:scaffold Post
126
- ```
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.
127
130
 
128
- The scaffold creates generated app code the Rails developer can own and 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.
129
135
 
130
136
  ```ruby
131
- # app/views/ruflet/components/posts/post_component.rb
132
- class PostComponent < Ruflet::Rails::ResourceComponent
133
- def render
134
- # edit the scaffold layout here
135
- 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
+ )
136
145
  end
137
146
  ```
138
147
 
139
- Mount it explicitly in `config/routes.rb`:
148
+ ### Crossing web and native from HTML
140
149
 
141
- ```ruby
142
- 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>
143
172
  ```
144
173
 
145
- The generated component contains the developer-owned UI and persistence calls.
146
- `ruflet_rails` provides the reusable model, navigation, dialog, and formatting
147
- helpers. Component files under `app/views/ruflet/components` are loaded by the
148
- Railtie so both web mounts and `main.rb` can reference them.
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 %>
201
+ ```
149
202
 
150
- ## Ruflet model forms
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:
151
207
 
152
- Generate only a reusable Ruflet form for an existing Rails model:
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 } %>
153
212
 
154
- ```bash
155
- bin/rails generate ruflet:form Post
213
+ <%= button_tag "Copy link",
214
+ data: { ruflet_action: { component: "toast", message: "Copied to clipboard" }.to_json } %>
156
215
  ```
157
216
 
158
- When no fields are passed, the generator reads the model columns and skips `id`,
159
- `created_at`, and `updated_at`. You can also pass fields explicitly:
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 title:string body:text published:boolean category:references
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
- Foreign keys and references, such as `category:references` or `user_id`, render
166
- as Ruflet dropdowns populated from the associated Rails model.
255
+ Use `action: "sheet"` to present a Rails URL inside a native bottom sheet whose
256
+ body is still a WebView:
167
257
 
168
- The generated form lives at `app/views/ruflet/components/posts/post_form.rb`.
169
- The form generator creates `app/views/ruflet/components/application_component.rb`
170
- when that base component does not already exist.
258
+ ```erb
259
+ <%= link_to "Choose language", languages_path,
260
+ data: { ruflet_action: { component: "sheet", url: languages_path }.to_json } %>
261
+ ```
171
262
 
172
- ## Shared Ruflet components
263
+ Inside a WebView sheet, plain Rails links are promoted to native actions so the
264
+ sheet closes before navigation:
173
265
 
174
- Put shared Ruflet UI components under `app/views/ruflet/components`. Those files
175
- are reloaded with Rails application code:
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) %>
271
+ ```
176
272
 
177
- ```ruby
178
- # app/views/ruflet/components/page_title_component.rb
179
- class PageTitleComponent < ApplicationComponent
180
- def render(value)
181
- text(value, size: desktop? || web? ? 28 : 24, weight: "bold")
182
- end
183
- end
273
+ Add `data-ruflet-close="false"` when a sheet link should not dismiss:
274
+
275
+ ```erb
276
+ <%= link_to "Preview", preview_path, data: { ruflet_close: "false" } %>
184
277
  ```
185
278
 
279
+ **Native services** — ERB can trigger safe platform services through the same
280
+ `data-ruflet-action` channel:
281
+
282
+ ```erb
283
+ <%= ruflet_share_link "Share", "#",
284
+ text: "Look at this", title: "My App" %>
285
+
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" %>
291
+ ```
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
+
186
296
  ```ruby
187
297
  # app/views/ruflet/main.rb
188
298
  Ruflet.run do |page|
189
- page.add(PageTitleComponent.render(page, "Posts"))
299
+ Ruflet::Rails.native_app(page, start_url: "#{Ruflet::Rails.backend_url}/")
190
300
  end
191
301
  ```
192
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