ruby_everywhere 0.3.0 → 0.5.0

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 (125) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +339 -0
  3. data/bridge/README.md +70 -1
  4. data/bridge/everywhere/bridge.js +211 -3
  5. data/bridge/everywhere/native.css +61 -0
  6. data/lib/everywhere/android_resources.rb +217 -0
  7. data/lib/everywhere/android_sdk.rb +330 -0
  8. data/lib/everywhere/asset_catalog.rb +185 -0
  9. data/lib/everywhere/auth_handoff.rb +404 -0
  10. data/lib/everywhere/auth_token.rb +118 -0
  11. data/lib/everywhere/builders/android.rb +844 -0
  12. data/lib/everywhere/builders/desktop.rb +326 -0
  13. data/lib/everywhere/builders/ios.rb +40 -3
  14. data/lib/everywhere/child_processes.rb +74 -0
  15. data/lib/everywhere/commands/build.rb +95 -14
  16. data/lib/everywhere/commands/clean.rb +10 -5
  17. data/lib/everywhere/commands/dev.rb +462 -57
  18. data/lib/everywhere/commands/doctor.rb +89 -3
  19. data/lib/everywhere/commands/install.rb +21 -1
  20. data/lib/everywhere/commands/logs.rb +117 -8
  21. data/lib/everywhere/commands/release.rb +2 -2
  22. data/lib/everywhere/config.rb +703 -13
  23. data/lib/everywhere/console.rb +117 -0
  24. data/lib/everywhere/desktop_assets.rb +150 -0
  25. data/lib/everywhere/dock/footer.rb +150 -0
  26. data/lib/everywhere/dock/screen.rb +114 -0
  27. data/lib/everywhere/dock/state.rb +59 -0
  28. data/lib/everywhere/dock.rb +238 -0
  29. data/lib/everywhere/emulator.rb +405 -0
  30. data/lib/everywhere/engine.rb +12 -0
  31. data/lib/everywhere/fatal.rb +20 -0
  32. data/lib/everywhere/icon.rb +196 -0
  33. data/lib/everywhere/line_pump.rb +89 -0
  34. data/lib/everywhere/log_filter.rb +111 -0
  35. data/lib/everywhere/native_helper.rb +39 -9
  36. data/lib/everywhere/omniauth.rb +88 -0
  37. data/lib/everywhere/paths.rb +109 -11
  38. data/lib/everywhere/relay.rb +77 -0
  39. data/lib/everywhere/shellout.rb +90 -15
  40. data/lib/everywhere/task_pool.rb +123 -0
  41. data/lib/everywhere/ui.rb +54 -11
  42. data/lib/everywhere/version.rb +1 -1
  43. data/support/desktop/README.md +121 -0
  44. data/support/{shell → desktop}/src-tauri/Cargo.lock +23 -0
  45. data/support/{shell → desktop}/src-tauri/Cargo.toml +19 -1
  46. data/support/{shell → desktop}/src-tauri/capabilities/default.json +7 -0
  47. data/support/desktop/src-tauri/gen/schemas/capabilities.json +1 -0
  48. data/support/desktop/src-tauri/src/extension_host.rs +53 -0
  49. data/support/desktop/src-tauri/src/extensions/mod.rs +39 -0
  50. data/support/{shell → desktop}/src-tauri/src/main.rs +355 -10
  51. data/support/{shell → desktop}/src-tauri/tauri.conf.json +2 -2
  52. data/support/mobile/android/README.md +217 -0
  53. data/support/mobile/android/app/build.gradle.kts +156 -0
  54. data/support/mobile/android/app/everywhere.properties +8 -0
  55. data/support/mobile/android/app/native-packages.gradle.kts +16 -0
  56. data/support/mobile/android/app/proguard-rules.pro +21 -0
  57. data/support/mobile/android/app/src/debug/res/xml/network_security_config.xml +30 -0
  58. data/support/mobile/android/app/src/main/AndroidManifest.xml +42 -0
  59. data/support/mobile/android/app/src/main/assets/everywhere.json +9 -0
  60. data/support/mobile/android/app/src/main/assets/fonts/MaterialIcons-Regular.codepoints +2235 -0
  61. data/support/mobile/android/app/src/main/assets/fonts/MaterialIcons-Regular.ttf +0 -0
  62. data/support/mobile/android/app/src/main/assets/json/path-configuration.json +19 -0
  63. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/AuthFlow.kt +268 -0
  64. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/EverywhereApplication.kt +178 -0
  65. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/EverywhereConfig.kt +409 -0
  66. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/EverywhereEvents.kt +68 -0
  67. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/EverywhereHost.kt +101 -0
  68. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/IconFont.kt +211 -0
  69. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/MainActivity.kt +975 -0
  70. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/MainTabs.kt +201 -0
  71. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/WebBottomSheetFragment.kt +13 -0
  72. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/WebFragment.kt +36 -0
  73. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/bridge/BiometricsComponent.kt +535 -0
  74. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/bridge/HapticsComponent.kt +141 -0
  75. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/bridge/MenuComponent.kt +361 -0
  76. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/bridge/NotificationComponent.kt +151 -0
  77. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/bridge/PermissionsComponent.kt +271 -0
  78. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/bridge/StorageComponent.kt +101 -0
  79. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/bridge/WebControlChannel.kt +204 -0
  80. data/support/mobile/android/app/src/main/java/com/rubyeverywhere/shell/extensions/EverywhereExtensions.kt +51 -0
  81. data/support/mobile/android/app/src/main/res/color/bottom_navigation_active_indicator.xml +4 -0
  82. data/support/mobile/android/app/src/main/res/color/bottom_navigation_item.xml +11 -0
  83. data/support/mobile/android/app/src/main/res/drawable/ic_launcher_foreground.xml +26 -0
  84. data/support/mobile/android/app/src/main/res/drawable/ic_tab_placeholder.xml +28 -0
  85. data/support/mobile/android/app/src/main/res/layout/activity_main.xml +116 -0
  86. data/support/mobile/android/app/src/main/res/layout/bridge_menu_sheet.xml +83 -0
  87. data/support/mobile/android/app/src/main/res/layout/bridge_menu_sheet_item.xml +35 -0
  88. data/support/mobile/android/app/src/main/res/layout/tab_more_sheet.xml +48 -0
  89. data/support/mobile/android/app/src/main/res/layout/tab_more_sheet_item.xml +35 -0
  90. data/support/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +12 -0
  91. data/support/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +6 -0
  92. data/support/mobile/android/app/src/main/res/values/colors.xml +21 -0
  93. data/support/mobile/android/app/src/main/res/values/ic_launcher_background.xml +5 -0
  94. data/support/mobile/android/app/src/main/res/values/strings.xml +18 -0
  95. data/support/mobile/android/app/src/main/res/values/styles.xml +32 -0
  96. data/support/mobile/android/app/src/main/res/values/themes.xml +53 -0
  97. data/support/mobile/android/app/src/main/res/values-night/colors.xml +11 -0
  98. data/support/mobile/android/app/src/main/res/values-night/themes.xml +33 -0
  99. data/support/mobile/android/app/src/main/res/xml/network_security_config.xml +12 -0
  100. data/support/mobile/android/app/src/stamped/AndroidManifest.xml +34 -0
  101. data/support/mobile/android/build.gradle.kts +8 -0
  102. data/support/mobile/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  103. data/support/mobile/android/gradle/wrapper/gradle-wrapper.properties +10 -0
  104. data/support/mobile/android/gradle.properties +14 -0
  105. data/support/mobile/android/gradlew +251 -0
  106. data/support/mobile/android/gradlew.bat +94 -0
  107. data/support/mobile/android/settings.gradle.kts +28 -0
  108. data/support/mobile/ios/App/AppDelegate.swift +5 -0
  109. data/support/mobile/ios/App/AuthFlow.swift +101 -0
  110. data/support/mobile/ios/App/EverywhereConfig.swift +84 -0
  111. data/support/mobile/ios/App/SceneDelegate.swift +103 -0
  112. data/support/mobile/ios/App.xcodeproj/project.pbxproj +4 -0
  113. data/support/mobile/ios/README.md +4 -1
  114. data/support/{macos → release/macos}/notarize.sh +2 -2
  115. metadata +97 -17
  116. data/support/github/build.yml +0 -85
  117. data/support/shell/src-tauri/gen/schemas/capabilities.json +0 -1
  118. /data/support/{shell → desktop}/splash/index.html +0 -0
  119. /data/support/{shell → desktop}/src-tauri/build.rs +0 -0
  120. /data/support/{shell → desktop}/src-tauri/gen/schemas/acl-manifests.json +0 -0
  121. /data/support/{shell → desktop}/src-tauri/gen/schemas/desktop-schema.json +0 -0
  122. /data/support/{shell → desktop}/src-tauri/gen/schemas/macOS-schema.json +0 -0
  123. /data/support/{shell → desktop}/src-tauri/icons/icon.png +0 -0
  124. /data/support/{shell → desktop}/src-tauri/src/updater.rs +0 -0
  125. /data/support/{macos → release/macos}/entitlements.plist +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b255975aa8724cb4980606ddbde415eea3a238c193ffce8530028ef5fdc63c24
4
- data.tar.gz: 386145d42d750984da4fa800ea9b63417371937dbf67d49642a229cd92cfbb71
3
+ metadata.gz: b4e222fd8e74b0c921b4cdab9d681b0f06bad2d94aae8dbe999415824508a1f5
4
+ data.tar.gz: e3a49bb93e2e49924c70cb622943e02b9bb302ddb828d29fc4c2ee5064c01e0e
5
5
  SHA512:
6
- metadata.gz: 6af24db6e60c693ae855fa47832564074d887c18248a63be23c343c5e85c7d84cf4210017ba6bb26dae9950bb6b1394255f02cbea4d278f9407952a22f37dd6c
7
- data.tar.gz: 368f12504a2a5748aa30eaf641eadf5b85bbd54b99e0f372015b9621510f01dc5779ca6e3e37cfab594083768815e40e48200e2c3649195c5b5e800cc7cf5557
6
+ metadata.gz: 5756a97bf1012daf2069fdb186de16f22a1bdbed9972ec42e384dc8b9b24a84b03b004568af26eeb6990b53336ceb3bfd6c4f3177ec9b0b43e2682db15b382a1
7
+ data.tar.gz: aba1e8c877b1ecd3b9b975a434c89831a9a0b302cbc2bea567472d650f28a442666735cfb9171a20aa615894c7e9bd5de52d2f9e334b7b9af00f750489fac14f
data/README.md ADDED
@@ -0,0 +1,339 @@
1
+ # RubyEverywhere
2
+
3
+ **Ship desktop and mobile apps from the Ruby web apps you already know how to build.**
4
+
5
+ [rubyeverywhere.com](https://rubyeverywhere.com) · by [Andrea Fomera](https://afomera.dev)
6
+
7
+ RubyEverywhere takes a Rails (or Sinatra, or Hanami) app and gives it a native
8
+ life on the platforms your users are already on:
9
+
10
+ - **Desktop** — a [Tauri](https://tauri.app)-based shell. In *local* mode your
11
+ whole app is pressed into a single-file binary with
12
+ [Tebako](https://github.com/tamatebako/tebako) and runs on-device, no Ruby
13
+ install required; in *remote* mode the shell is a thin native wrapper around
14
+ your deployed app.
15
+ - **iOS** — a [Hotwire Native](https://native.hotwired.dev) shell with a real
16
+ tab bar, nav-bar buttons, menus, action sheets, notifications, haptics,
17
+ biometrics, deep links, and native sign-in.
18
+ - **Android** — a feature-for-feature Hotwire Native Android port of the same
19
+ shell, stamped from the same `config/everywhere.yml`.
20
+
21
+ One config file, one CLI, one JavaScript API. The web app stays the source of
22
+ truth — most of what you'd normally rebuild natively (tabs, routes, menus,
23
+ badges) deploys with your Rails app rather than through an app-store review.
24
+
25
+ ## Install
26
+
27
+ ```sh
28
+ gem install ruby_everywhere
29
+ ```
30
+
31
+ This gives you two equivalent executables — `every` and `rbe` — plus the
32
+ packaging toolchain (the `rbe-tebako` fork) as a dependency.
33
+
34
+ Or add it to your app's Gemfile, which also wires up the Rails engine (bridge
35
+ asset serving, importmap pin, view helpers, the live mobile config endpoint):
36
+
37
+ ```ruby
38
+ gem "ruby_everywhere"
39
+ ```
40
+
41
+ Requires Ruby >= 3.2.
42
+
43
+ **The gem is free, and it always will be.** Everything here — the CLI, the
44
+ desktop shell, both mobile shells, the bridge — is MIT licensed and yours to
45
+ use, forever, with no account and no key. Local builds run on a Mac today
46
+ (pressing, code signing, Xcode and the Android SDK all want a macOS host).
47
+ If you don't own one, or you'd rather not keep a build toolchain healthy, the
48
+ [RubyEverywhere Platform](https://rubyeverywhere.com) is a paid service coming
49
+ soon that runs the exact same builds for you — see
50
+ [Hosted builds](#hosted-builds) below. It's a convenience, never a gate: nothing
51
+ in this gem is held back for it.
52
+
53
+ ## Quick start
54
+
55
+ ```sh
56
+ cd path/to/your/app
57
+
58
+ every install # remote mode: a native shell around your deployed app
59
+ every install --local # local mode: compile and ship the app itself
60
+
61
+ every doctor # check the toolchain before your first build
62
+
63
+ every dev # dev server + native shells on demand, live reload
64
+ every dev --ios # ...straight into the iOS Simulator
65
+ every dev --android # ...straight into the Android emulator
66
+ every dev --mobile # ...both at once, built in parallel
67
+
68
+ every build # package the macOS .app
69
+ every build --ios # build the iOS shell
70
+ every build --android # build the APK/AAB
71
+ ```
72
+
73
+ `every install` is idempotent — it adds `config/everywhere.yml`, a boot stub,
74
+ and the framework-specific config tweaks it needs, and skips anything already
75
+ in place. For Rails it does the surgery for you; for Sinatra and Hanami it
76
+ vendors the bridge and prints the remaining wiring rather than guessing at your
77
+ boot files.
78
+
79
+ `every dev` never packages anything: it runs your ordinary dev server and points
80
+ the native shells at it, so the edit-refresh loop is untouched Rails.
81
+
82
+ It keeps a dock pinned to the bottom of the terminal showing what each target is
83
+ doing, while the logs scroll normally above it — ordinary scrollback, so you can
84
+ still scroll up and search a backtrace:
85
+
86
+ ```
87
+ web │ Started GET "/notes" for 127.0.0.1
88
+ ios │ ▸ compiling Swift sources
89
+ ────────────────────────────────────────────────────────────────
90
+ ● web :3000 ○ desktop ⠙ iOS 42s ✗ Android see dist/android-build.log
91
+ ⌨ d desktop · i iOS · a Android · b browser · l logs · r restart · ? help · q quit
92
+ ```
93
+
94
+ Every target runs on its own thread, so builds happen in parallel and the keys
95
+ keep working while one is going. A build that fails marks that target red and
96
+ leaves everything else — the dev server included — running; fix the error and
97
+ press the key again. `r` restarts just the dev server, `?` expands the full key
98
+ list, and each line is tagged with the source that printed it once more than one
99
+ thing is talking.
100
+
101
+ Set `EVERY_NO_DOCK=1` (or pipe the output anywhere) to get plain, unadorned
102
+ lines instead.
103
+
104
+ ## Configuration
105
+
106
+ Everything per-app lives in `config/everywhere.yml`. The native shell templates
107
+ are frozen — the CLI stamps a copy per app and only ever writes the identity,
108
+ config, icon, and color files, so a shell upgrade never collides with your app.
109
+
110
+ ```yaml
111
+ app:
112
+ name: My Really Awesome App
113
+ bundle_id: com.example.myapp # set it once; renaming moves users' data
114
+ version: 1.2.0
115
+ entry_path: /dashboard # where the app opens
116
+ icon: icon.png
117
+
118
+ appearance:
119
+ tint_color: "#CC342D"
120
+ background_color: # one hex string, or split by system theme
121
+ light: "#FAF9F7"
122
+ dark: "#1C1B1A"
123
+
124
+ remote:
125
+ url: https://app.example.com # remote mode: the deployed app to wrap
126
+
127
+ window: # desktop window chrome
128
+ title_bar: overlay # decorated (default) | overlay | frameless
129
+ title: false # draw the title text
130
+ size: [1100, 750]
131
+ min_size: [600, 400]
132
+ drag_height: 28 # top strip that drags the window; 0 to opt out
133
+
134
+ tabs: # the native mobile tab bar
135
+ - name: Builds
136
+ path: /builds
137
+ icons:
138
+ ios: hammer # SF Symbol
139
+ android: build # Material icon
140
+
141
+ permissions: # only declared permissions can ever prompt
142
+ notifications: true
143
+ camera: "Scan QR codes to pair devices." # the sentence iOS shows
144
+
145
+ rules: # Hotwire Native path configuration
146
+ - patterns: ["/preferences$"]
147
+ properties:
148
+ context: modal
149
+
150
+ native: # code compiled into the native shells
151
+ desktop:
152
+ commands: [scan_ports] # Rust in native/desktop/, callable from the page
153
+ crates:
154
+ serialport: "4.3"
155
+
156
+ build:
157
+ ruby: "4.0.6"
158
+ targets: [macos-arm64, ios-arm64, android-arm64]
159
+
160
+ updates: # self-hosted, signed auto-updates
161
+ url: https://updates.example.com
162
+ public_key: RWQ…
163
+ auto: check # off | check | download | install
164
+ ```
165
+
166
+ Also supported: `auth:` (native OAuth sign-in with a callback scheme),
167
+ `deep_linking:` (universal links / App Links — the gem serves the
168
+ `apple-app-site-association` and `assetlinks.json` files for you), and
169
+ `native:` for "supernative" extensions — Swift you keep in your app repo under
170
+ `native/ios/` that `every build` compiles into the shell, including SwiftUI
171
+ screens, bridge components, a custom splash, and pinned SPM packages.
172
+
173
+ Desktop has the same seam in `native/desktop/`, shaped for what a desktop app
174
+ actually wants: not native screens (the whole UI is a webview) but the machine
175
+ underneath it. Drop in Rust, pin the crates you need, and call it from the page:
176
+
177
+ ```yaml
178
+ native:
179
+ desktop:
180
+ commands: [scan_ports]
181
+ setup: true # run native/desktop/setup.rs at boot
182
+ crates:
183
+ serialport: "4.3"
184
+ ```
185
+
186
+ ```rust
187
+ // native/desktop/serial.rs
188
+ pub fn scan_ports(_app: &tauri::AppHandle, payload: serde_json::Value)
189
+ -> Result<serde_json::Value, String> { … }
190
+ ```
191
+
192
+ ```js
193
+ const ports = await Everywhere.desktop.invoke("scan_ports", { baud: 9600 })
194
+ ```
195
+
196
+ Declaring anything under `native.desktop` has a real cost the mobile sections
197
+ don't: the desktop shell stops being one prebuilt binary shared by every app and
198
+ gets compiled per app, so the first build takes minutes instead of seconds. Apps
199
+ that declare nothing are untouched.
200
+
201
+ Images the desktop shell itself draws live in `native/desktop/assets/`, using the
202
+ same `<name>[@2x|@3x][~dark].png` filenames the mobile assets folders use.
203
+ Name one `tray` (or `tray-template` for a macOS template image) and it becomes
204
+ the menu-bar icon; a custom `splash:` can reference the rest as
205
+ `assets/logo.png`. These are for the shell, not your pages — images your HTML
206
+ references should keep coming from the asset pipeline.
207
+
208
+ `window:` shapes the desktop frame. `title_bar: overlay` is the macOS look where
209
+ the traffic lights float over your page and there's no title text — pair it with
210
+ the `.everywhere-titlebar-inset` class from `everywhere/native.css`, which
211
+ reserves exactly the space they sit in. `frameless` removes the system chrome
212
+ altogether; the page then draws its own bar and drives the buttons through
213
+ `Everywhere.window`. Windows and Linux have no overlay style, so `overlay`
214
+ behaves as `frameless` there.
215
+
216
+ Either way the window stays draggable with no markup: the shell makes the top
217
+ 28px of the page drag it (and double-click zoom it), while buttons and links up
218
+ there keep their clicks. `window.drag_height: 0` hands that back to you.
219
+
220
+ Tabs and path rules ship two ways from that one source: baked into the app
221
+ bundle for offline/first launch, and served live from
222
+ `/everywhere/{ios,android}_v1.json`, so changing them deploys with your web app.
223
+ `Everywhere.filter_tabs` lets you vary the tab bar per request — it shares the
224
+ app's session, so signed-out users can get a different bar without a release.
225
+
226
+ ## The JavaScript bridge
227
+
228
+ The gem ships `@rubyeverywhere/bridge` — one API for the browser, the desktop
229
+ shell, and the mobile shells. Rails apps get it served and importmap-pinned
230
+ automatically; Sinatra and Hanami get it vendored to `public/bridge.js`.
231
+
232
+ ```js
233
+ import Everywhere from "@rubyeverywhere/bridge"
234
+
235
+ Everywhere.platform // "desktop" | "mobile" | "browser"
236
+ Everywhere.native // true inside a RubyEverywhere app
237
+
238
+ Everywhere.notify({ title, body })
239
+ Everywhere.menu({ title, items })
240
+ Everywhere.haptics.impact("medium")
241
+ Everywhere.badge.setTab("/inbox", 3)
242
+ Everywhere.permissions.request("camera")
243
+ Everywhere.storage.set("theme", "dark")
244
+ Everywhere.updates.check()
245
+ Everywhere.window.close() // desktop, for a page-drawn title bar
246
+ ```
247
+
248
+ Everything degrades gracefully — the same page works in a plain browser tab and
249
+ gains native powers inside the app. See
250
+ [`bridge/README.md`](bridge/README.md) for the full surface.
251
+
252
+ Most native chrome needs no JavaScript at all. Tag ordinary markup and the
253
+ bridge lifts it into real native controls; tapping the native control just
254
+ `.click()`s the element it mirrors, so behavior stays defined once in the DOM.
255
+ Rails ships helpers that emit exactly that markup:
256
+
257
+ ```erb
258
+ <%= everywhere_nav_button "New", new_note_path, icons: { ios: "plus", android: "add" } %>
259
+ <%= everywhere_fab new_note_path %>
260
+ <%= everywhere_nav_menu "More" do %>
261
+ <%= everywhere_menu_item "Delete", note_path(@note), method: :delete, style: "destructive" %>
262
+ <% end %>
263
+ ```
264
+
265
+ Plus `native_app?`, `native_platform`, `everywhere_badge`,
266
+ `everywhere_biometric_lock`, and friends.
267
+
268
+ ## Commands
269
+
270
+ | Command | What it does |
271
+ | --- | --- |
272
+ | `every install` | Prepare an app (boot stub, `config/everywhere.yml`, config tweaks) |
273
+ | `every doctor` | Check the toolchain — Tebako, Rust, brew deps, Xcode, Android SDK/JDK |
274
+ | `every dev` | Dev server with native shells on demand (`--desktop --ios --android --mobile --browser`) |
275
+ | `every logs` | Stream shell logs from the Simulator or Android device |
276
+ | `every build` | Package natively — desktop, `--ios`, or `--android` |
277
+ | `every icon` | Generate `.icns` / `.ico` / Linux icons from a source PNG |
278
+ | `every release` | Sign, notarize, staple, and emit a `release.json` receipt |
279
+ | `every updates keygen` | Generate a minisign-compatible update-signing keypair |
280
+ | `every publish` | Publish a release to your update bucket |
281
+ | `every clean` | Remove the shell build caches |
282
+ | `every shell-dir` | Print the path of the bundled shell template |
283
+ | `every platform …` | Hosted builds: `login`, `logout`, `auth status`, `build` |
284
+
285
+ Run `every` with no arguments for the full list, or `every <command> --help` for
286
+ a command's flags.
287
+
288
+ ## Auto-updates
289
+
290
+ Desktop releases can update themselves from a bucket you own, with no service in
291
+ the middle:
292
+
293
+ ```sh
294
+ every updates keygen # once, per app
295
+ every release # build, sign, notarize, staple
296
+ every publish --notes-file NOTES.md # sign the manifest, upload, flip latest.json
297
+ ```
298
+
299
+ The public key ships in the app; the secret key never leaves your machine. Apps
300
+ can check, install, and switch channels at runtime via `Everywhere.updates`.
301
+
302
+ ## Hosted builds
303
+
304
+ Don't have a Mac, or want iOS and Android built without babysitting Xcode, the
305
+ Android SDK, and a signing setup? The **RubyEverywhere Platform** — a paid
306
+ service, coming soon — runs the same builds on managed machines and hands back
307
+ the artifacts. The CLI already speaks to it:
308
+
309
+ ```sh
310
+ every platform login
311
+ every platform build --target ios-arm64,android-arm64
312
+ ```
313
+
314
+ Same gem, same `config/everywhere.yml`, same output — you're paying for the
315
+ build machines and the toolchain upkeep, not for features. The gem stays free
316
+ and complete on its own.
317
+
318
+ ## Project layout
319
+
320
+ ```
321
+ lib/everywhere/ the CLI, config, builders, and Rails engine
322
+ exe/every, exe/rbe the executables
323
+ bridge/ @rubyeverywhere/bridge — the JS half
324
+ support/desktop/ the Tauri desktop shell template
325
+ support/mobile/ios/ the Hotwire Native iOS shell template
326
+ support/mobile/android/ the Hotwire Native Android shell template
327
+ ```
328
+
329
+ The shell templates have their own READMEs documenting the freeze rules — what
330
+ the CLI stamps per app and what it must never touch:
331
+ [iOS](support/mobile/ios/README.md) · [Android](support/mobile/android/README.md).
332
+
333
+ ## Contributing
334
+
335
+ Bug reports and pull requests are welcome. Run the test suite with `rake test`.
336
+
337
+ ## License
338
+
339
+ Released under the [MIT License](LICENSE.txt). Copyright © 2026 Andrea Fomera.
data/bridge/README.md CHANGED
@@ -135,6 +135,75 @@ tap, and `<meta name="everywhere:badge">` / `<meta name="everywhere:tab-badge">`
135
135
  tags (the `everywhere_badge` / `everywhere_tab_badge` Rails helpers) are
136
136
  applied on every Turbo visit.
137
137
 
138
+ ### Desktop extensions
139
+
140
+ Rust the app keeps in `native/desktop/` and declares under
141
+ `native.desktop.commands` in `everywhere.yml`:
142
+
143
+ ```js
144
+ Everywhere.desktop.supported // true in the desktop shell
145
+
146
+ const ports = await Everywhere.desktop.invoke("scan_ports", { baud: 9600 })
147
+ ```
148
+
149
+ The payload arrives in Rust as a `serde_json::Value` and whatever the command
150
+ returns comes back as the resolved value; a command that returns `Err` rejects
151
+ with its own message.
152
+
153
+ Unlike the rest of the bridge this does **not** degrade — there's no web
154
+ equivalent for "talk to a serial port", so off the desktop it rejects rather
155
+ than resolving something untrue. Gate on `Everywhere.desktop.supported`.
156
+
157
+ Under the hood this is an event round-trip, not Tauri's `invoke`: a
158
+ RubyEverywhere page is always served over http, and Tauri v2 refuses `invoke`
159
+ from any http origin — including the packaged app's own localhost server.
160
+
161
+ ### Window controls
162
+
163
+ Desktop apps that draw their own title bar — `window.title_bar: overlay` or
164
+ `frameless` in `everywhere.yml` — drive the window from the page:
165
+
166
+ ```js
167
+ Everywhere.window.supported // true in the desktop shell
168
+
169
+ Everywhere.window.minimize()
170
+ Everywhere.window.maximize()
171
+ Everywhere.window.unmaximize()
172
+ Everywhere.window.toggleMaximize() // what a title-bar double-click does
173
+ Everywhere.window.close()
174
+ await Everywhere.window.isMaximized() // Promise<boolean>
175
+ Everywhere.window.startDragging() // from a pointerdown on a custom bar
176
+ ```
177
+
178
+ `overlay` keeps the macOS traffic lights and only hides the title, so the page
179
+ just needs to stay clear of them; `frameless` removes every control, so the page
180
+ supplies its own.
181
+
182
+ **Dragging works out of the box.** With no system title bar left to grab, the
183
+ shell makes the top 28px of the page drag the window, and double-click there
184
+ zooms it — no markup required. Buttons, links and inputs in that strip keep
185
+ their clicks:
186
+
187
+ ```html
188
+ <header class="everywhere-titlebar">
189
+ <span>My App</span>
190
+ <button onclick="Everywhere.window.close()">×</button>
191
+ </header>
192
+ ```
193
+
194
+ To make something *outside* the strip draggable, mark it
195
+ `data-tauri-drag-region`. To own dragging yourself, set `window.drag_height: 0`
196
+ in `everywhere.yml` and call `startDragging()` from your own handlers.
197
+
198
+ `everywhere/native.css` ships `.everywhere-titlebar` (a drag strip that clears
199
+ the traffic lights in overlay mode) plus `.everywhere-titlebar-inset` and
200
+ `.everywhere-titlebar-controls-inset` padding utilities. They key off an
201
+ attribute the bridge stamps on `<html>`, so they're inert in a browser tab, on
202
+ mobile, and in a normal decorated window.
203
+
204
+ Everything here resolves to `null` off the desktop, so the same markup is safe
205
+ everywhere.
206
+
138
207
  ### Auto-updates
139
208
 
140
209
  Inside the desktop shell, apps with an update feed configured can check,
@@ -164,7 +233,7 @@ In a plain browser tab the updates API is inert: `check()` resolves
164
233
  ## Platforms
165
234
 
166
235
  - **desktop** — the Tauri-based RubyEverywhere shell. Notifications, dialogs,
167
- clipboard, shell events, and auto-updates are native.
236
+ clipboard, shell events, window controls, and auto-updates are native.
168
237
  - **browser** — honest web fallbacks: the Notification API, `window.confirm`,
169
238
  `navigator.clipboard`, DOM CustomEvents.
170
239
  - **mobile** — Hotwire Native (iOS shell). `notify()` delivers a native local