ruby_everywhere 0.13.0 → 0.14.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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +30 -30
  3. data/bridge/README.md +23 -23
  4. data/lib/everywhere/android_resources.rb +9 -9
  5. data/lib/everywhere/android_sdk.rb +9 -9
  6. data/lib/everywhere/asset_catalog.rb +7 -7
  7. data/lib/everywhere/blake2b.rb +1 -1
  8. data/lib/everywhere/boot.rb +1 -1
  9. data/lib/everywhere/builders/android.rb +25 -25
  10. data/lib/everywhere/builders/desktop.rb +6 -6
  11. data/lib/everywhere/builders/ios.rb +14 -14
  12. data/lib/everywhere/builders/native_sources.rb +1 -1
  13. data/lib/everywhere/child_supervision.rb +1 -1
  14. data/lib/everywhere/commands/build.rb +19 -19
  15. data/lib/everywhere/commands/clean.rb +1 -1
  16. data/lib/everywhere/commands/dev.rb +11 -11
  17. data/lib/everywhere/commands/doctor.rb +7 -7
  18. data/lib/everywhere/commands/icon.rb +2 -2
  19. data/lib/everywhere/commands/install.rb +21 -21
  20. data/lib/everywhere/commands/logs.rb +6 -6
  21. data/lib/everywhere/commands/platform/auth_status.rb +3 -3
  22. data/lib/everywhere/commands/platform/build.rb +7 -7
  23. data/lib/everywhere/commands/platform/login.rb +5 -5
  24. data/lib/everywhere/commands/platform/logout.rb +1 -1
  25. data/lib/everywhere/commands/platform/runner.rb +6 -6
  26. data/lib/everywhere/commands/preview.rb +14 -14
  27. data/lib/everywhere/commands/publish.rb +14 -14
  28. data/lib/everywhere/commands/release.rb +6 -6
  29. data/lib/everywhere/commands/updates_keygen.rb +4 -4
  30. data/lib/everywhere/config/app.rb +3 -3
  31. data/lib/everywhere/config/mobile.rb +2 -2
  32. data/lib/everywhere/config/native_desktop.rb +2 -2
  33. data/lib/everywhere/config/native_mobile.rb +3 -3
  34. data/lib/everywhere/config/updates.rb +2 -2
  35. data/lib/everywhere/database.rb +3 -3
  36. data/lib/everywhere/desktop_assets.rb +5 -5
  37. data/lib/everywhere/desktop_dev_app.rb +1 -1
  38. data/lib/everywhere/emulator.rb +5 -5
  39. data/lib/everywhere/log_filter.rb +1 -1
  40. data/lib/everywhere/omniauth.rb +1 -1
  41. data/lib/everywhere/paths.rb +1 -1
  42. data/lib/everywhere/platform/credentials.rb +1 -1
  43. data/lib/everywhere/request_context.rb +2 -2
  44. data/lib/everywhere/shellout.rb +2 -2
  45. data/lib/everywhere/simulator.rb +1 -1
  46. data/lib/everywhere/tab_filter.rb +2 -2
  47. data/lib/everywhere/version.rb +1 -1
  48. data/support/agents/AGENTS.md +7 -7
  49. data/support/agents/frameworks/hanami-views.md +8 -8
  50. data/support/agents/frameworks/hanami.md +2 -2
  51. data/support/agents/frameworks/rails-views.md +7 -7
  52. data/support/agents/frameworks/rails.md +8 -8
  53. data/support/agents/frameworks/sinatra-views.md +7 -7
  54. data/support/agents/frameworks/sinatra.md +2 -2
  55. data/support/agents/modes/local.md +2 -2
  56. data/support/agents/modes/remote.md +2 -2
  57. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0afe4b448a6f964a0b013d31762aac332b71682717c04d25194271d9d1c92b67
4
- data.tar.gz: '039d951d08c4d3be2d8a22c4de5a862d5dc1ca9635b978f989d4ba56807da148'
3
+ metadata.gz: f8b84ded1729bc011133caca7923d38ef48fc832193bd960228332b04d299717
4
+ data.tar.gz: 52164a6c960be3ff61ebd969ca037df1c23c8843fa00f89a570f043814f68805
5
5
  SHA512:
6
- metadata.gz: 2656b49706209ee27c7aeb0410ea9ebff73ee833f58df43b9d51ceb2049b2e7540436b561e4a301fa89e6afb87c48056d1b0556598982cfaafb4367be55f8266
7
- data.tar.gz: aca89fa867ba1aa8a1053e0d2324e358a02d9dd94e19129d984ca593e9598d3f031c91d95077b037fcb86a3a11d04535a0b70695f4eb3443ea7548f825617d83
6
+ metadata.gz: 37ee8e1843a575bc4678fd47c56d23d8108dd33af708a06e8317b32179837bb951e8cd396dfca8acb6ec3b84942bee9bd9b5089a8d853c57150fcabe1a651633
7
+ data.tar.gz: 442223b8697ab79c0bb7f0d63379cef3bc8453203707d2d4f7c835c85f60253049fb68d54f499c0f1d2b87dda4b3988b5042ef3b40457c0f31de380da1f5db8d
data/README.md CHANGED
@@ -7,19 +7,19 @@
7
7
  RubyEverywhere takes a Rails (or Sinatra, or Hanami) app and gives it a native
8
8
  life on the platforms your users are already on:
9
9
 
10
- - **Desktop** a [Tauri](https://tauri.app)-based shell. In *local* mode your
10
+ - **Desktop**: a [Tauri](https://tauri.app)-based shell. In *local* mode your
11
11
  whole app is pressed into a single-file binary with
12
12
  [Tebako](https://github.com/tamatebako/tebako) and runs on-device, no Ruby
13
13
  install required; in *remote* mode the shell is a thin native wrapper around
14
14
  your deployed app.
15
- - **iOS** a [Hotwire Native](https://native.hotwired.dev) shell with a real
15
+ - **iOS**: a [Hotwire Native](https://native.hotwired.dev) shell with a real
16
16
  tab bar, nav-bar buttons, menus, action sheets, notifications, haptics,
17
17
  biometrics, deep links, and native sign-in.
18
- - **Android** a feature-for-feature Hotwire Native Android port of the same
18
+ - **Android**: a feature-for-feature Hotwire Native Android port of the same
19
19
  shell, stamped from the same `config/everywhere.yml`.
20
20
 
21
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,
22
+ truth, so most of what you'd normally rebuild natively (tabs, routes, menus,
23
23
  badges) deploys with your Rails app rather than through an app-store review.
24
24
 
25
25
  ## Install
@@ -28,7 +28,7 @@ badges) deploys with your Rails app rather than through an app-store review.
28
28
  gem install ruby_everywhere
29
29
  ```
30
30
 
31
- This gives you two equivalent executables `every` and `rbe` plus the
31
+ This gives you two equivalent executables, `every` and `rbe`, plus the
32
32
  packaging toolchain (the `rbe-tebako` fork) as a dependency.
33
33
 
34
34
  Or add it to your app's Gemfile, which also wires up the Rails engine (bridge
@@ -40,13 +40,13 @@ gem "ruby_everywhere"
40
40
 
41
41
  Requires Ruby >= 3.2.
42
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
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
45
  use, forever, with no account and no key. Local builds run on a Mac today
46
46
  (pressing, code signing, Xcode and the Android SDK all want a macOS host).
47
47
  If you don't own one, or you'd rather not keep a build toolchain healthy, the
48
48
  [RubyEverywhere Platform](https://rubyeverywhere.com) is a paid service coming
49
- soon that runs the exact same builds for you — see
49
+ soon that runs the exact same builds for you. See
50
50
  [Hosted builds](#hosted-builds) below. It's a convenience, never a gate: nothing
51
51
  in this gem is held back for it.
52
52
 
@@ -71,14 +71,14 @@ every build --ios # build the iOS shell
71
71
  every build --android # build the APK/AAB
72
72
  ```
73
73
 
74
- `every install` is idempotent it adds `config/everywhere.yml`, a boot stub,
74
+ `every install` is idempotent: it adds `config/everywhere.yml`, a boot stub,
75
75
  and the framework-specific config tweaks it needs, and skips anything already
76
76
  in place. For Rails it does the surgery for you; for Sinatra and Hanami it
77
77
  vendors the bridge and prints the remaining wiring rather than guessing at your
78
78
  boot files.
79
79
 
80
- Working with an AI coding agent? `--agents` writes an `AGENTS.md` the format
81
- Claude Code, Cursor, Copilot and Codex all read covering the view helpers
80
+ Working with an AI coding agent? `--agents` writes an `AGENTS.md`, the format
81
+ Claude Code, Cursor, Copilot and Codex all read, covering the view helpers
82
82
  (`native_app?`, `everywhere_nav_button`, `everywhere_fab`, the menus and badges),
83
83
  `config/everywhere.yml`, the bridge API, and the `every` commands, so an agent
84
84
  reaches for the right helper instead of hand-rolling markup. It's framework- and
@@ -91,7 +91,7 @@ re-running refreshes our section and leaves your own notes alone.
91
91
  the native shells at it, so the edit-refresh loop is untouched Rails.
92
92
 
93
93
  It keeps a dock pinned to the bottom of the terminal showing what each target is
94
- doing, while the logs scroll normally above it ordinary scrollback, so you can
94
+ doing, while the logs scroll normally above it: ordinary scrollback, so you can
95
95
  still scroll up and search a backtrace:
96
96
 
97
97
  ```
@@ -104,7 +104,7 @@ ios │ ▸ compiling Swift sources
104
104
 
105
105
  Every target runs on its own thread, so builds happen in parallel and the keys
106
106
  keep working while one is going. A build that fails marks that target red and
107
- leaves everything else the dev server included — running; fix the error and
107
+ leaves everything else running, the dev server included; fix the error and
108
108
  press the key again. `r` restarts just the dev server, `?` expands the full key
109
109
  list, and each line is tagged with the source that printed it once more than one
110
110
  thing is talking.
@@ -115,7 +115,7 @@ lines instead.
115
115
  ## Configuration
116
116
 
117
117
  Everything per-app lives in `config/everywhere.yml`. The native shell templates
118
- are frozen the CLI stamps a copy per app and only ever writes the identity,
118
+ are frozen: the CLI stamps a copy per app and only ever writes the identity,
119
119
  config, icon, and color files, so a shell upgrade never collides with your app.
120
120
 
121
121
  ```yaml
@@ -175,9 +175,9 @@ updates: # self-hosted, signed auto-updates
175
175
  ```
176
176
 
177
177
  Also supported: `auth:` (native OAuth sign-in with a callback scheme),
178
- `deep_linking:` (universal links / App Links the gem serves the
178
+ `deep_linking:` (universal links / App Links, where the gem serves the
179
179
  `apple-app-site-association` and `assetlinks.json` files for you), and
180
- `native:` for "supernative" extensions Swift you keep in your app repo under
180
+ `native:` for "supernative" extensions: Swift you keep in your app repo under
181
181
  `native/ios/` that `every build` compiles into the shell, including SwiftUI
182
182
  screens, bridge components, a custom splash, and pinned SPM packages.
183
183
 
@@ -213,11 +213,11 @@ Images the desktop shell itself draws live in `native/desktop/assets/`, using th
213
213
  same `<name>[@2x|@3x][~dark].png` filenames the mobile assets folders use.
214
214
  Name one `tray` (or `tray-template` for a macOS template image) and it becomes
215
215
  the menu-bar icon; a custom `splash:` can reference the rest as
216
- `assets/logo.png`. These are for the shell, not your pages images your HTML
216
+ `assets/logo.png`. These are for the shell, not your pages. Images your HTML
217
217
  references should keep coming from the asset pipeline.
218
218
 
219
219
  `window:` shapes the desktop frame. `title_bar: overlay` is the macOS look where
220
- the traffic lights float over your page and there's no title text pair it with
220
+ the traffic lights float over your page and there's no title text; pair it with
221
221
  the `.everywhere-titlebar-inset` class from `everywhere/native.css`, which
222
222
  reserves exactly the space they sit in. `frameless` removes the system chrome
223
223
  altogether; the page then draws its own bar and drives the buttons through
@@ -231,7 +231,7 @@ there keep their clicks. `window.drag_height: 0` hands that back to you.
231
231
  Tabs and path rules ship two ways from that one source: baked into the app
232
232
  bundle for offline/first launch, and served live from
233
233
  `/everywhere/{ios,android}_v1.json`, so changing them deploys with your web app.
234
- `Everywhere.tabs` lets you build the tab bar per request feature flags, plan
234
+ `Everywhere.tabs` lets you build the tab bar per request: feature flags, plan
235
235
  tiers, roles, anything your app can answer while serving a request:
236
236
 
237
237
  ```ruby
@@ -251,12 +251,12 @@ end
251
251
 
252
252
  The context carries `platform` (`"ios"`/`"android"`), `current_user`, and
253
253
  `session`/`cookies` normalized across Rails and Rack. `ctx.tab("Admin")` pulls in
254
- a tab declared `visible: false` in `everywhere.yml` declared so the build
254
+ a tab declared `visible: false` in `everywhere.yml`, declared so the build
255
255
  validates its icon, hidden so nothing ships it until this block says otherwise.
256
256
 
257
257
  ## The JavaScript bridge
258
258
 
259
- The gem ships `@rubyeverywhere/bridge` one API for the browser, the desktop
259
+ The gem ships `@rubyeverywhere/bridge`: one API for the browser, the desktop
260
260
  shell, and the mobile shells. Rails apps get it served and importmap-pinned
261
261
  automatically; Sinatra and Hanami get it vendored to `public/bridge.js`.
262
262
 
@@ -276,7 +276,7 @@ Everywhere.updates.check()
276
276
  Everywhere.window.close() // desktop, for a page-drawn title bar
277
277
  ```
278
278
 
279
- Everything degrades gracefully the same page works in a plain browser tab and
279
+ Everything degrades gracefully, and the same page works in a plain browser tab and
280
280
  gains native powers inside the app. See
281
281
  [`bridge/README.md`](bridge/README.md) for the full surface.
282
282
 
@@ -300,11 +300,11 @@ Plus `native_app?`, `native_platform`, `everywhere_badge`,
300
300
 
301
301
  | Command | What it does |
302
302
  | --- | --- |
303
- | `every install` | Prepare an app (boot stub, `config/everywhere.yml`, config tweaks) `--local`, `--agents` |
304
- | `every doctor` | Check the toolchain Tebako, Rust, brew deps, Xcode, Android SDK/JDK |
303
+ | `every install` | Prepare an app (boot stub, `config/everywhere.yml`, config tweaks); `--local`, `--agents` |
304
+ | `every doctor` | Check the toolchain: Tebako, Rust, brew deps, Xcode, Android SDK/JDK |
305
305
  | `every dev` | Dev server with native shells on demand (`--desktop --ios --android --mobile --browser`) |
306
306
  | `every logs` | Stream shell logs from the Simulator or Android device |
307
- | `every build` | Package natively desktop, `--ios`, or `--android` |
307
+ | `every build` | Package natively: desktop, `--ios`, or `--android` |
308
308
  | `every icon` | Generate `.icns` / `.ico` / Linux icons from a source PNG |
309
309
  | `every release` | Sign, notarize, staple, and emit a `release.json` receipt |
310
310
  | `every updates keygen` | Generate a minisign-compatible update-signing keypair |
@@ -335,8 +335,8 @@ check, install, and switch channels at runtime via `Everywhere.updates`.
335
335
  ## Hosted builds
336
336
 
337
337
  Don't have a Mac, or want iOS and Android built without babysitting Xcode, the
338
- Android SDK, and a signing setup? The **RubyEverywhere Platform** a paid
339
- service, coming soon runs the same builds on managed machines and hands back
338
+ Android SDK, and a signing setup? The **RubyEverywhere Platform**, a paid
339
+ service coming soon, runs the same builds on managed machines and hands back
340
340
  the artifacts. The CLI already speaks to it:
341
341
 
342
342
  ```sh
@@ -344,7 +344,7 @@ every platform login
344
344
  every platform build --target ios-arm64,android-arm64
345
345
  ```
346
346
 
347
- Same gem, same `config/everywhere.yml`, same output — you're paying for the
347
+ Same gem, same `config/everywhere.yml`, same output. You're paying for the
348
348
  build machines and the toolchain upkeep, not for features. The gem stays free
349
349
  and complete on its own.
350
350
 
@@ -353,14 +353,14 @@ and complete on its own.
353
353
  ```
354
354
  lib/everywhere/ the CLI, config, builders, and Rails engine
355
355
  exe/every, exe/rbe the executables
356
- bridge/ @rubyeverywhere/bridge the JS half
356
+ bridge/ @rubyeverywhere/bridge, the JS half
357
357
  support/desktop/ the Tauri desktop shell template
358
358
  support/mobile/ios/ the Hotwire Native iOS shell template
359
359
  support/mobile/android/ the Hotwire Native Android shell template
360
360
  support/agents/ the AGENTS.md guide `every install --agents` writes
361
361
  ```
362
362
 
363
- The shell templates have their own READMEs documenting the freeze rules what
363
+ The shell templates have their own READMEs documenting the freeze rules: what
364
364
  the CLI stamps per app and what it must never touch:
365
365
  [iOS](support/mobile/ios/README.md) · [Android](support/mobile/android/README.md).
366
366
 
data/bridge/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @rubyeverywhere/bridge
2
2
 
3
- One API for the browser, the RubyEverywhere desktop shell, and (soon) Hotwire
4
- Native mobile apps the JavaScript half of [RubyEverywhere](https://rubyeverywhere.com).
3
+ One API for the browser, the RubyEverywhere desktop shell, and the Hotwire
4
+ Native iOS and Android shells: the JavaScript half of [RubyEverywhere](https://rubyeverywhere.com).
5
5
 
6
6
  App code writes to this surface only; platform differences live in adapters
7
7
  inside the package. Everything degrades gracefully: the same page works in a
@@ -33,7 +33,7 @@ Everywhere.confirm("Sure?") // Promise<boolean>, native dialog when pos
33
33
  Everywhere.on("menu", handler) // shell events; returns an unsubscribe fn
34
34
  Everywhere.visit("/settings") // Turbo.visit with location fallback
35
35
 
36
- Everywhere.menu({ title, items }) // Promise<item | null> native action
36
+ Everywhere.menu({ title, items }) // Promise<item | null>, native action
37
37
  // sheet in the shell, a styled bottom
38
38
  // sheet everywhere else. items:
39
39
  // [{ title, id?, style?, disabled? }]
@@ -55,25 +55,25 @@ Everywhere.permissions.request("camera") // prompts when possible
55
55
  Everywhere.permissions.openSettings() // for the denied dead-end (iOS asks once)
56
56
  // status: "granted" | "denied" | "prompt" | "undeclared" | "unsupported"
57
57
  // mobile permissions must be declared in everywhere.yml (camera/location
58
- // declarations carry the usage string iOS shows) undeclared never prompts
58
+ // declarations carry the usage string iOS shows); undeclared never prompts
59
59
 
60
- Everywhere.storage.get("key") // Promise<any | null> device-local settings
60
+ Everywhere.storage.get("key") // Promise<any | null>, device-local settings
61
61
  Everywhere.storage.set("key", value) // Promise<boolean>; any JSON value round-trips
62
62
  Everywhere.storage.remove("key") // Promise<boolean>
63
- Everywhere.storage.clear() // Promise<boolean> clears app keys only
63
+ Everywhere.storage.clear() // Promise<boolean>, clears app keys only
64
64
  // UserDefaults in the mobile shell (survives web view resets and cache
65
65
  // clears, unlike localStorage); localStorage-backed in browsers and the
66
- // desktop shell. Settings, not secrets tokens go in biometrics.credential.
66
+ // desktop shell. Settings, not secrets: tokens go in biometrics.credential.
67
67
  ```
68
68
 
69
69
  ### Native chrome (nav bar buttons, menus, action sheets)
70
70
 
71
71
  Tag ordinary markup, and inside the mobile shell the bridge lifts it into real
72
- native controls a nav-bar button, a pull-down / overflow menu, or an action
72
+ native controls: a nav-bar button, a pull-down / overflow menu, or an action
73
73
  sheet. Tapping the native control just `.click()`s the element it mirrors, so a
74
74
  link navigates, a submit submits, and behavior stays defined once in the DOM. In
75
75
  a browser the same markup is the plain HTML it already is. No JavaScript, no
76
- Stimulus CSP-safe.
76
+ Stimulus, so it clears CSP.
77
77
 
78
78
  ```html
79
79
  <!-- becomes a nav-bar button (right side); icons are per-platform like tabs -->
@@ -103,7 +103,7 @@ Stimulus — CSP-safe.
103
103
  </div>
104
104
  ```
105
105
 
106
- Rails ships helpers that emit exactly this `everywhere_nav_button`,
106
+ Rails ships helpers that emit exactly this: `everywhere_nav_button`,
107
107
  `everywhere_submit_button`, `everywhere_nav_menu` / `everywhere_menu_item`,
108
108
  `everywhere_menu`, and `everywhere_fab` (a floating action button). Include
109
109
  `everywhere/native.css` for the FAB, the action-sheet fallback, and the
@@ -111,7 +111,7 @@ safe-area utilities.
111
111
 
112
112
  ### Multi-instance apps
113
113
 
114
- Apps whose users pick a server one hosted platform, many instances opt in
114
+ Apps whose users pick a server, one hosted platform with many instances, opt in
115
115
  with `remote.instances: true` in `everywhere.yml`. The mobile shell then boots
116
116
  into `remote.url` (your hosted picker page) until that page picks an instance:
117
117
 
@@ -120,13 +120,13 @@ Everywhere.instance.supported // true in a shell built with remote.instan
120
120
  Everywhere.instance.current // active instance URL, or null (= built-in root)
121
121
 
122
122
  Everywhere.instance.set("https://acme.example.com")
123
- // validates, persists, and fully resets the app onto the instance — every
123
+ // validates, persists, and fully resets the app onto the instance. Every
124
124
  // later launch boots straight there. {to: "/path"} lands somewhere specific.
125
125
 
126
126
  Everywhere.instance.clear() // back to the picker (and resets again)
127
127
  ```
128
128
 
129
- In a plain browser `set()` simply navigates to the URL going there is what
129
+ In a plain browser `set()` navigates to the URL: going there is what
130
130
  picking an instance means on the web.
131
131
 
132
132
  Declarative forms need no JS: elements with `data-everywhere-haptic`
@@ -150,18 +150,18 @@ The payload arrives in Rust as a `serde_json::Value` and whatever the command
150
150
  returns comes back as the resolved value; a command that returns `Err` rejects
151
151
  with its own message.
152
152
 
153
- Unlike the rest of the bridge this does **not** degrade there's no web
153
+ Unlike the rest of the bridge this does **not** degrade; there's no web
154
154
  equivalent for "talk to a serial port", so off the desktop it rejects rather
155
155
  than resolving something untrue. Gate on `Everywhere.desktop.supported`.
156
156
 
157
157
  Under the hood this is an event round-trip, not Tauri's `invoke`: a
158
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.
159
+ from any http origin, including the packaged app's own localhost server.
160
160
 
161
161
  ### Window controls
162
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:
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
165
 
166
166
  ```js
167
167
  Everywhere.window.supported // true in the desktop shell
@@ -181,7 +181,7 @@ supplies its own.
181
181
 
182
182
  **Dragging works out of the box.** With no system title bar left to grab, the
183
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
184
+ zooms it, with no markup required. Buttons, links and inputs in that strip keep
185
185
  their clicks:
186
186
 
187
187
  ```html
@@ -232,16 +232,16 @@ In a plain browser tab the updates API is inert: `check()` resolves
232
232
 
233
233
  ## Platforms
234
234
 
235
- - **desktop** the Tauri-based RubyEverywhere shell. Notifications, dialogs,
235
+ - **desktop**: the Tauri-based RubyEverywhere shell. Notifications, dialogs,
236
236
  clipboard, shell events, window controls, and auto-updates are native.
237
- - **browser** honest web fallbacks: the Notification API, `window.confirm`,
237
+ - **browser**: honest web fallbacks: the Notification API, `window.confirm`,
238
238
  `navigator.clipboard`, DOM CustomEvents.
239
- - **mobile** Hotwire Native (iOS shell). `notify()` delivers a native local
239
+ - **mobile**: the Hotwire Native iOS and Android shells. `notify()` delivers a native local
240
240
  notification via the shell's `everywhere--notification` bridge component
241
241
  (web-bridge handshake is installed automatically unless the app already
242
- loads `@hotwired/hotwire-native-bridge` first one wins, like the official
242
+ loads `@hotwired/hotwire-native-bridge`; first one wins, like the official
243
243
  package). `confirm()`/clipboard use the browser fallbacks, which are native
244
- in a WKWebView anyway. Everything degrades to browser behavior outside the
244
+ in the web view anyway. Everything degrades to browser behavior outside the
245
245
  shell.
246
246
 
247
247
  ## License
@@ -100,10 +100,10 @@ module Everywhere
100
100
  relative = relative(path, source)
101
101
  case File.extname(path).downcase
102
102
  when ".svg", ".pdf"
103
- "#{relative}: Android has no runtime SVG or PDF renderer export a PNG, or convert it to " \
103
+ "#{relative}: Android has no runtime SVG or PDF renderer. Export a PNG, or convert it to " \
104
104
  "a VectorDrawable (Android Studio: right-click res → New → Vector Asset) and ship the .xml"
105
105
  else
106
- "#{relative}: unsupported image type use #{EXTENSIONS.join(", ")}"
106
+ "#{relative}: unsupported image type. Use #{EXTENSIONS.join(", ")}"
107
107
  end
108
108
  end
109
109
 
@@ -116,7 +116,7 @@ module Everywhere
116
116
  # resource name may — so it would otherwise fail with a naming error
117
117
  # that says nothing about what the author actually tried to do.
118
118
  if stem.end_with?(".9")
119
- errors << "#{relative}: nine-patch drawables aren't supported yet ship a plain #{extension} " \
119
+ errors << "#{relative}: nine-patch drawables aren't supported yet. Ship a plain #{extension} " \
120
120
  "(or a VectorDrawable .xml, which stretches without one)"
121
121
  return nil
122
122
  end
@@ -124,23 +124,23 @@ module Everywhere
124
124
  name, scale, dark = parse(stem)
125
125
 
126
126
  unless name.match?(VALID_NAME)
127
- errors << "#{relative}: #{name.inspect} isn't a usable Android resource name use lowercase " \
127
+ errors << "#{relative}: #{name.inspect} isn't a usable Android resource name. Use lowercase " \
128
128
  "letters, digits and _, starting with a letter (#{suggestion(name).inspect} would " \
129
129
  "work). Unlike iOS, Android has no room for capitals, hyphens or dots here"
130
130
  return nil
131
131
  end
132
132
  if JAVA_KEYWORDS.include?(name)
133
- errors << "#{relative}: #{name.inspect} is a Java keyword, so it can't name a field on R " \
133
+ errors << "#{relative}: #{name.inspect} is a Java keyword, so it can't name a field on R. " \
134
134
  "rename it (#{"#{name}_image".inspect})"
135
135
  return nil
136
136
  end
137
137
  if RESERVED.include?(name)
138
- errors << "#{relative}: #{name} is reserved by the shell (launcher icon and tab placeholder) " \
138
+ errors << "#{relative}: #{name} is reserved by the shell (launcher icon and tab placeholder). " \
139
139
  "rename it"
140
140
  return nil
141
141
  end
142
142
  if scale && VECTOR.include?(extension)
143
- errors << "#{relative}: an XML drawable is scale-free drop the @#{scale} suffix"
143
+ errors << "#{relative}: an XML drawable is scale-free. Drop the @#{scale} suffix"
144
144
  return nil
145
145
  end
146
146
 
@@ -190,11 +190,11 @@ module Everywhere
190
190
  dirs = group.map { |variant| variant[:source_dir] }.uniq
191
191
  if dirs.length > 1
192
192
  listed = dirs.map { |dir| dir == "." ? "assets/" : "assets/#{dir}/" }.join(" and ")
193
- errors << "#{name}: same asset name in #{listed} res/ is flat, so both would become " \
193
+ errors << "#{name}: same asset name in #{listed}. res/ is flat, so both would become " \
194
194
  "R.drawable.#{name}. Rename one"
195
195
  end
196
196
  if group.map { |variant| variant[:vector] }.uniq.length > 1
197
- errors << "#{name}: mixes an XML drawable with bitmaps pick one per asset"
197
+ errors << "#{name}: mixes an XML drawable with bitmaps. Pick one per asset"
198
198
  end
199
199
 
200
200
  # Same destination directory and same name: aapt2 sees two files
@@ -98,7 +98,7 @@ module Everywhere
98
98
  end
99
99
 
100
100
  def sdk_root!
101
- sdk_root or UI.die!("no Android SDK install Android Studio (it provisions the SDK, adb, " \
101
+ sdk_root or UI.die!("no Android SDK. Install Android Studio (it provisions the SDK, adb, " \
102
102
  "the emulator and a JDK in one go), or point ANDROID_HOME at an " \
103
103
  "existing SDK; looked in #{expected_sdk_root}")
104
104
  end
@@ -115,22 +115,22 @@ module Everywhere
115
115
  def emulator = sdk_tool("emulator", "emulator")
116
116
 
117
117
  def adb!
118
- adb or UI.die!("no adb under #{sdk_root!} #{install_hint("platform-tools")}")
118
+ adb or UI.die!("no adb under #{sdk_root!}: #{install_hint("platform-tools")}")
119
119
  end
120
120
 
121
121
  def emulator!
122
- emulator or UI.die!("no Android emulator under #{sdk_root!} #{install_hint("emulator")}")
122
+ emulator or UI.die!("no Android emulator under #{sdk_root!}: #{install_hint("emulator")}")
123
123
  end
124
124
 
125
125
  def sdkmanager = cmdline_tool("sdkmanager")
126
126
  def avdmanager = cmdline_tool("avdmanager")
127
127
 
128
128
  def sdkmanager!
129
- sdkmanager or UI.die!("no sdkmanager under #{sdk_root!} #{cmdline_tools_hint}")
129
+ sdkmanager or UI.die!("no sdkmanager under #{sdk_root!}: #{cmdline_tools_hint}")
130
130
  end
131
131
 
132
132
  def avdmanager!
133
- avdmanager or UI.die!("no avdmanager under #{sdk_root!} #{cmdline_tools_hint}")
133
+ avdmanager or UI.die!("no avdmanager under #{sdk_root!}: #{cmdline_tools_hint}")
134
134
  end
135
135
 
136
136
  # Whether this SDK can install anything at all. Worth asking on its own,
@@ -237,12 +237,12 @@ module Everywhere
237
237
  # where the message can name the cause.
238
238
  def java_home!(minimum: MINIMUM_JDK)
239
239
  home = java_home or
240
- UI.die!("no JDK install Android Studio (it bundles one at #{STUDIO_JBR.first}) " \
240
+ UI.die!("no JDK. Install Android Studio (it bundles one at #{STUDIO_JBR.first}) " \
241
241
  "or set JAVA_HOME to a JDK #{minimum} or newer")
242
242
  version = java_version
243
243
  return home if version.nil? || version >= minimum
244
244
 
245
- UI.die!("#{home} is JDK #{version}; the Android Gradle Plugin needs #{minimum} or newer " \
245
+ UI.die!("#{home} is JDK #{version}; the Android Gradle Plugin needs #{minimum} or newer. " \
246
246
  "set JAVA_HOME to a JDK #{minimum}+, or install Android Studio and use its bundled runtime")
247
247
  end
248
248
 
@@ -321,7 +321,7 @@ module Everywhere
321
321
 
322
322
  package = "platforms;#{platform_name(api)}"
323
323
  UI.die!("the Android SDK has no #{package} (installed: " \
324
- "#{installed_platforms.empty? ? "none" : installed_platforms.join(", ")}) " \
324
+ "#{installed_platforms.empty? ? "none" : installed_platforms.join(", ")}). " \
325
325
  "#{install_hint(package)}")
326
326
  end
327
327
 
@@ -347,7 +347,7 @@ module Everywhere
347
347
  found = avds
348
348
  return found if found.any?
349
349
 
350
- UI.die!("no Android virtual devices #{create_avd_hint}")
350
+ UI.die!("no Android virtual devices. #{create_avd_hint}")
351
351
  end
352
352
 
353
353
  # How to get a bootable AVD. The GUI route comes first because a machine
@@ -57,15 +57,15 @@ module Everywhere
57
57
  images, other = files.partition { |path| EXTENSIONS.include?(File.extname(path).downcase) }
58
58
 
59
59
  other.each do |path|
60
- errors << "#{relative(path, source)}: unsupported image type use #{EXTENSIONS.join(", ")}"
60
+ errors << "#{relative(path, source)}: unsupported image type. Use #{EXTENSIONS.join(", ")}"
61
61
  end
62
62
  variants = images.filter_map { |path| variant(path, source, errors) }
63
63
 
64
64
  sets = variants.group_by { |v| v[:name] }.map do |name, group|
65
65
  dirs = group.map { |v| v[:dir] }.uniq
66
66
  if dirs.length > 1
67
- errors << "#{name}: same asset name in #{dirs.map { |d| d == "." ? "assets/" : "assets/#{d}/" }.join(" and ")} " \
68
- " names are global in the catalog, so rename one"
67
+ errors << "#{name}: same asset name in #{dirs.map { |d| d == "." ? "assets/" : "assets/#{d}/" }.join(" and ")}. " \
68
+ "Names are global in the catalog, so rename one"
69
69
  end
70
70
  errors.concat(slot_errors(name, group))
71
71
  { name: name, dir: dirs.first, variants: group }
@@ -80,16 +80,16 @@ module Everywhere
80
80
  name, scale, dark = parse(File.basename(path, File.extname(path)))
81
81
 
82
82
  unless name.match?(VALID_NAME)
83
- errors << "#{relative}: #{name.inspect} isn't a usable asset name " \
83
+ errors << "#{relative}: #{name.inspect} isn't a usable asset name. " \
84
84
  "use letters, digits, _, - and ."
85
85
  return nil
86
86
  end
87
87
  if RESERVED.include?(name)
88
- errors << "#{relative}: #{name} is reserved (stamped from config/everywhere.yml) rename it"
88
+ errors << "#{relative}: #{name} is reserved (stamped from config/everywhere.yml). Rename it"
89
89
  return nil
90
90
  end
91
91
  if scale && VECTOR.include?(extension)
92
- errors << "#{relative}: vector assets are scale-free drop the @#{scale} suffix"
92
+ errors << "#{relative}: vector assets are scale-free. Drop the @#{scale} suffix"
93
93
  return nil
94
94
  end
95
95
 
@@ -117,7 +117,7 @@ module Everywhere
117
117
  def slot_errors(name, group)
118
118
  errors = []
119
119
  if group.map { |v| v[:vector] }.uniq.length > 1
120
- errors << "#{name}: mixes vector and raster files pick one per asset"
120
+ errors << "#{name}: mixes vector and raster files. Pick one per asset"
121
121
  end
122
122
  group.group_by { |v| [v[:scale], v[:dark]] }.each_value do |clash|
123
123
  next if clash.length < 2
@@ -90,7 +90,7 @@ module Everywhere
90
90
  @warned_fallback = true
91
91
  require_relative "ui"
92
92
  mb = (File.size(path) / 1024.0 / 1024.0).round(1)
93
- Everywhere::UI.warn("this Ruby's OpenSSL has no BLAKE2b512 hashing #{mb}MB in pure Ruby " \
93
+ Everywhere::UI.warn("this Ruby's OpenSSL has no BLAKE2b512, so hashing #{mb}MB in pure Ruby " \
94
94
  "(slow, and the file is read into memory)")
95
95
  end
96
96
 
@@ -88,7 +88,7 @@ module Everywhere
88
88
  ENV[key] or raise Error, app_data_hint("#{key} is unset")
89
89
  end
90
90
 
91
- def app_data_hint(reason) = "can't work out where to keep app data (#{reason}) " \
91
+ def app_data_hint(reason) = "can't work out where to keep app data (#{reason}). " \
92
92
  "set NATIVE_APPDATA (or HOME / APPDATA)"
93
93
 
94
94
  def prepare_app_data