ruby_everywhere 0.5.0 → 0.6.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b4e222fd8e74b0c921b4cdab9d681b0f06bad2d94aae8dbe999415824508a1f5
4
- data.tar.gz: e3a49bb93e2e49924c70cb622943e02b9bb302ddb828d29fc4c2ee5064c01e0e
3
+ metadata.gz: 1c87aa5a2416d070a8047b790f28c7230882999b7df4f7f584256ee96852f066
4
+ data.tar.gz: 6fa25612acf17e6d90b3302a4e7f93a8d5ded79fd8ffb39aec318b908df32bcf
5
5
  SHA512:
6
- metadata.gz: 5756a97bf1012daf2069fdb186de16f22a1bdbed9972ec42e384dc8b9b24a84b03b004568af26eeb6990b53336ceb3bfd6c4f3177ec9b0b43e2682db15b382a1
7
- data.tar.gz: aba1e8c877b1ecd3b9b975a434c89831a9a0b302cbc2bea567472d650f28a442666735cfb9171a20aa615894c7e9bd5de52d2f9e334b7b9af00f750489fac14f
6
+ metadata.gz: 69fb1bdb659b8b886d9b602e09e21d8016caf140ac401ee371d4b0eaab2e06f3b342fb7742e2684570b80e71768fb2c5ede34dca3f1d5b9015185b93b176ba59
7
+ data.tar.gz: 450a804d3bf9765ea664e0728c66df31019e5633393680120ef9ec7ba40def7e69f163b9aaaf6b2d2fcec948e62c5d3821b26bb553924666dfb57b7d1c6b29cd
data/README.md CHANGED
@@ -57,6 +57,7 @@ cd path/to/your/app
57
57
 
58
58
  every install # remote mode: a native shell around your deployed app
59
59
  every install --local # local mode: compile and ship the app itself
60
+ every install --agents # ...plus an AGENTS.md guide for AI coding agents
60
61
 
61
62
  every doctor # check the toolchain before your first build
62
63
 
@@ -76,6 +77,16 @@ in place. For Rails it does the surgery for you; for Sinatra and Hanami it
76
77
  vendors the bridge and prints the remaining wiring rather than guessing at your
77
78
  boot files.
78
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
82
+ (`native_app?`, `everywhere_nav_button`, `everywhere_fab`, the menus and badges),
83
+ `config/everywhere.yml`, the bridge API, and the `every` commands, so an agent
84
+ reaches for the right helper instead of hand-rolling markup. It's framework- and
85
+ mode-aware, and adds a one-line `CLAUDE.md` pointer if you don't already have
86
+ one. If you already have an `AGENTS.md`, only the block between
87
+ `<!-- rubyeverywhere:begin -->` and `<!-- rubyeverywhere:end -->` is written, so
88
+ re-running refreshes our section and leaves your own notes alone.
89
+
79
90
  `every dev` never packages anything: it runs your ordinary dev server and points
80
91
  the native shells at it, so the edit-refresh loop is untouched Rails.
81
92
 
@@ -269,7 +280,7 @@ Plus `native_app?`, `native_platform`, `everywhere_badge`,
269
280
 
270
281
  | Command | What it does |
271
282
  | --- | --- |
272
- | `every install` | Prepare an app (boot stub, `config/everywhere.yml`, config tweaks) |
283
+ | `every install` | Prepare an app (boot stub, `config/everywhere.yml`, config tweaks) — `--local`, `--agents` |
273
284
  | `every doctor` | Check the toolchain — Tebako, Rust, brew deps, Xcode, Android SDK/JDK |
274
285
  | `every dev` | Dev server with native shells on demand (`--desktop --ios --android --mobile --browser`) |
275
286
  | `every logs` | Stream shell logs from the Simulator or Android device |
@@ -324,6 +335,7 @@ bridge/ @rubyeverywhere/bridge — the JS half
324
335
  support/desktop/ the Tauri desktop shell template
325
336
  support/mobile/ios/ the Hotwire Native iOS shell template
326
337
  support/mobile/android/ the Hotwire Native Android shell template
338
+ support/agents/ the AGENTS.md guide `every install --agents` writes
327
339
  ```
328
340
 
329
341
  The shell templates have their own READMEs documenting the freeze rules — what
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "paths"
4
+ require_relative "ui"
5
+
6
+ module Everywhere
7
+ # The AGENTS.md guide `every install --agents` writes into an app, so AI coding
8
+ # agents (Claude Code, Cursor, Copilot, Codex) understand what RubyEverywhere is
9
+ # before they "fix" the config edits install made.
10
+ #
11
+ # The guide is prose, so it lives as markdown under support/agents/ rather than
12
+ # in a heredoc — code fences and #{} snippets are miserable to escape in Ruby,
13
+ # and the gemspec already globs support/**/*. Assembly is a shared body plus
14
+ # per-framework fragments (install notes, view helpers) and a mode fragment,
15
+ # joined by {{PLACEHOLDER}} gsub.
16
+ module AgentsGuide
17
+ module_function
18
+
19
+ # The generated section is fenced so a re-run can refresh it without touching
20
+ # anything the app's own authors wrote around it.
21
+ BEGIN_MARKER = "<!-- rubyeverywhere:begin -->"
22
+ END_MARKER = "<!-- rubyeverywhere:end -->"
23
+
24
+ PLACEHOLDER = /\{\{([A-Z_]+)\}\}/
25
+ LABELS = { "rails" => "Rails", "sinatra" => "Sinatra", "hanami" => "Hanami" }.freeze
26
+
27
+ def template_dir = File.join(Paths.gem_root, "support", "agents")
28
+
29
+ # The guide body for one app, with no markers around it.
30
+ def render(framework:, mode:)
31
+ substitute(read("AGENTS.md"),
32
+ "FRAMEWORK" => LABELS.fetch(framework),
33
+ "MODE" => mode,
34
+ "FRAMEWORK_NOTES" => read("frameworks", "#{framework}.md").strip,
35
+ "FRAMEWORK_VIEWS" => read("frameworks", "#{framework}-views.md").strip,
36
+ "MODE_NOTES" => read("modes", "#{mode}.md").strip)
37
+ end
38
+
39
+ # The body wrapped in the markers, ready to splice into an AGENTS.md.
40
+ def block(framework:, mode:)
41
+ "#{BEGIN_MARKER}\n#{render(framework: framework, mode: mode).strip}\n#{END_MARKER}\n"
42
+ end
43
+
44
+ # fetch, not [], so a typo'd placeholder blows up in the test suite instead of
45
+ # shipping "{{MDOE}}" to somebody's repo.
46
+ def substitute(body, values)
47
+ body.gsub(PLACEHOLDER) { values.fetch(Regexp.last_match(1)) }
48
+ end
49
+
50
+ def read(*parts)
51
+ path = File.join(template_dir, *parts)
52
+ File.exist?(path) or UI.die!("couldn't find the bundled AGENTS.md template at #{path}; " \
53
+ "reinstall ruby_everywhere")
54
+ File.read(path)
55
+ end
56
+ end
57
+ end
@@ -3,6 +3,8 @@
3
3
  require "dry/cli"
4
4
  require "fileutils"
5
5
  require_relative "../shellout"
6
+ require_relative "../config"
7
+ require_relative "../agents_guide"
6
8
 
7
9
  module Everywhere
8
10
  module Commands
@@ -13,8 +15,9 @@ module Everywhere
13
15
 
14
16
  option :root, default: ".", desc: "App root directory"
15
17
  option :local, type: :boolean, default: false, desc: "Configure local mode (compile & ship the app) instead of remote mode"
18
+ option :agents, type: :boolean, default: false, desc: "Write an AGENTS.md guide so AI coding agents understand this app"
16
19
 
17
- def call(root: ".", local: false, **)
20
+ def call(root: ".", local: false, agents: false, **)
18
21
  @mode = local ? "local" : "remote"
19
22
  @framework = Framework.detect(root)
20
23
  @root = @framework.root
@@ -29,6 +32,13 @@ module Everywhere
29
32
  # Framework-specific config surgery.
30
33
  send("install_#{@framework.name}")
31
34
 
35
+ if agents
36
+ write_agents_guide
37
+ write_claude_pointer
38
+ else
39
+ UI.step(UI.dim("tip: `every install --agents` writes an AGENTS.md guide for AI coding agents"))
40
+ end
41
+
32
42
  UI.success("installed — run `every dev` to open the app, `every build` to package it")
33
43
  end
34
44
 
@@ -247,6 +257,68 @@ module Everywhere
247
257
  change("create native_boot.rb", true)
248
258
  end
249
259
 
260
+ # --- AGENTS.md (--agents) --------------------------------------------------
261
+
262
+ FRESH_HEADER = "# AGENTS.md\n\nGuidance for AI coding agents working in this repository.\n\n"
263
+
264
+ # AGENTS.md is the app's file, not ours — most apps that have one already
265
+ # have notes in it. So we own only what's between the markers, and a re-run
266
+ # refreshes that region in place.
267
+ def write_agents_guide
268
+ path = app_file("AGENTS.md")
269
+ block = AgentsGuide.block(framework: @framework.name, mode: guide_mode)
270
+
271
+ unless File.exist?(path)
272
+ File.write(path, FRESH_HEADER + block)
273
+ return change("create AGENTS.md", true)
274
+ end
275
+
276
+ existing = File.read(path)
277
+ updated = merge_agents_block(existing, block) or return # markers are broken; warned
278
+ File.write(path, updated) unless updated == existing
279
+ change("refresh the RubyEverywhere section of AGENTS.md", updated != existing)
280
+ end
281
+
282
+ # Returns the merged file, or nil when the markers are malformed — a lone
283
+ # BEGIN would make the next run's region match swallow everything the app's
284
+ # authors wrote after it, so we refuse rather than guess at a repair.
285
+ def merge_agents_block(existing, block)
286
+ begins = existing.scan(AgentsGuide::BEGIN_MARKER).size
287
+ ends = existing.scan(AgentsGuide::END_MARKER).size
288
+
289
+ if begins.zero? && ends.zero?
290
+ return existing.strip.empty? ? block : "#{existing.chomp}\n\n#{block}"
291
+ end
292
+
293
+ if begins != 1 || ends != 1 ||
294
+ existing.index(AgentsGuide::BEGIN_MARKER) > existing.index(AgentsGuide::END_MARKER)
295
+ UI.warn("AGENTS.md has unbalanced #{AgentsGuide::BEGIN_MARKER} / #{AgentsGuide::END_MARKER} " \
296
+ "markers — leaving it alone; fix them by hand and re-run")
297
+ return nil
298
+ end
299
+
300
+ region = /^#{Regexp.escape(AgentsGuide::BEGIN_MARKER)}\n.*?^#{Regexp.escape(AgentsGuide::END_MARKER)}\n?/m
301
+ existing.sub(region) { block }
302
+ end
303
+
304
+ # Claude Code reads AGENTS.md on its own; the pointer is for the setups that
305
+ # only look at CLAUDE.md. An app that already has one has its own reasons for
306
+ # what's in it — leave it alone and let AGENTS.md be found the normal way.
307
+ def write_claude_pointer
308
+ claude = app_file("CLAUDE.md")
309
+ return change("create CLAUDE.md pointer", false) if File.exist?(claude)
310
+
311
+ File.write(claude, "See @AGENTS.md for how this app works and how RubyEverywhere packages it.\n")
312
+ change("create CLAUDE.md pointer", true)
313
+ end
314
+
315
+ # --local only describes what THIS run configured. Once config/everywhere.yml
316
+ # exists it is the truth, so a re-run without the flag can't mislabel a
317
+ # local-mode app as remote.
318
+ def guide_mode
319
+ File.exist?(app_file(Config::FILE)) ? Config.load(@root).mode : @mode
320
+ end
321
+
250
322
  def guard_bootsnap
251
323
  boot = app_file("config", "boot.rb")
252
324
  contents = File.read(boot)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Everywhere
4
- VERSION = "0.5.0"
4
+ VERSION = "0.6.0"
5
5
 
6
6
  # Version of the @rubyeverywhere/bridge JS this gem ships. bridge/ in the
7
7
  # gem IS the npm package (served to Rails apps by Everywhere::Engine,
@@ -0,0 +1,90 @@
1
+ ## RubyEverywhere
2
+
3
+ This is a {{FRAMEWORK}} app that also ships as a native desktop and mobile app, via the
4
+ [`ruby_everywhere`](https://rubyeverywhere.com) gem. The desktop shell is Tauri, the mobile
5
+ shells are Hotwire Native, and all three are driven from one config file
6
+ (`config/everywhere.yml`), one CLI (`every`, aliased `rbe`), and one JavaScript API
7
+ (`Everywhere`) with server-side helpers to match.
8
+
9
+ **The web app is the source of truth.** You do not edit the shells — you edit the app, its
10
+ views, and the config. Full docs: <https://rubyeverywhere.com/docs>.
11
+
12
+ ### How this app reaches its users: {{MODE}} mode
13
+
14
+ {{MODE_NOTES}}
15
+
16
+ {{FRAMEWORK_VIEWS}}
17
+
18
+ ### config/everywhere.yml
19
+
20
+ Everything native is declared here: tabs, path rules, permissions, window chrome, menus,
21
+ tray items, colors, deep links, auto-updates. Top-level keys: `app`, `remote`, `appearance`,
22
+ `tabs`, `rules`, `permissions`, `menu`, `tray`, `window`, `native`, `auth`, `deep_linking`,
23
+ `build`, `updates`, `platforms`. Full reference:
24
+ <https://rubyeverywhere.com/docs/shared/everywhere-yml>.
25
+
26
+ Things that bite:
27
+
28
+ - **`app.bundle_id` is identity, not cosmetics.** It names the app-data directory on every
29
+ platform (and the macOS bundle id). Renaming it orphans users' local data and settings.
30
+ - **Permissions gate everything on mobile.** A permission that isn't declared under
31
+ `permissions:` resolves to `undeclared` and never prompts. `camera` and `location` must carry
32
+ the sentence iOS shows when asking, or the build fails.
33
+ - **`tabs:` and `rules:` deploy with the web app.** They're baked into the bundle *and* served
34
+ live from `/everywhere/ios_v1.json` and `/everywhere/android_v1.json`, so changing a tab or a
35
+ presentation rule ships with your next deploy — no app-store release.
36
+ - Most other keys (`window`, `menu`, `tray`, `permissions`, `appearance`) are read at build
37
+ time and do need a new build.
38
+
39
+ ### The JavaScript bridge
40
+
41
+ One API across browser, desktop, and mobile. It degrades gracefully in a plain browser, so
42
+ you can call it unconditionally.
43
+
44
+ ```js
45
+ import Everywhere from "@rubyeverywhere/bridge"
46
+ window.Everywhere = Everywhere
47
+ ```
48
+
49
+ Surface: `platform` (`"desktop" | "mobile" | "browser"`), `os`, `native`, `version`,
50
+ `notify()`, `confirm()`, `on()`, `visit()`, `menu()`, `reloadTabs()`, `resetApp()`, and the
51
+ namespaces `auth`, `clipboard`, `haptics`, `permissions`, `biometrics`, `instance`, `storage`,
52
+ `badge`, `desktop` (`desktop.invoke`), `window` (`minimize`/`maximize`/`close`/`startDragging`/…),
53
+ and `updates` (`check`/`install`/`setChannel`/`on`).
54
+
55
+ Reach for the bridge when something has to happen in response to JS. For anything you can
56
+ render, prefer the markup above — it's CSP-safe, correct on first paint, and works in a
57
+ browser without a code path of its own.
58
+
59
+ ### Commands
60
+
61
+ Run these from the app root.
62
+
63
+ | Command | What it does |
64
+ | --- | --- |
65
+ | `every dev` | Dev server plus native shells on demand, live reload (`--desktop --ios --android --mobile --browser`) |
66
+ | `every doctor` | Check the toolchain before a first build |
67
+ | `every build` | Build the native shells — desktop, or `--ios` / `--android` |
68
+ | `every logs` | Stream shell logs from the iOS Simulator or an Android device |
69
+ | `every icon` | Generate `.icns` / `.ico` / Linux icons from a source PNG |
70
+ | `every release` | Sign, notarize, staple, and emit `dist/release.json` |
71
+ | `every publish` | Publish a release to the app's update bucket |
72
+ | `every clean` | Remove the shell build caches |
73
+
74
+ `every dev` never packages anything — it runs the ordinary dev server and points the shells
75
+ at it, so the edit-refresh loop is unchanged.
76
+
77
+ Two directories are build output, never source: `dist/` (artifacts) and `~/.rubyeverywhere`
78
+ (stamped shell projects and caches). Don't commit them, don't hand-edit them — the next build
79
+ overwrites both.
80
+
81
+ ### Config `every install` touched
82
+
83
+ These exist so this codebase can also be compiled and shipped on-device, and several of them
84
+ look like mistakes if you don't know that. Leave them in place — full rationale at
85
+ <https://rubyeverywhere.com/docs>.
86
+
87
+ {{FRAMEWORK_NOTES}}
88
+
89
+ Re-running `every install` is idempotent and skips anything already in place, so it's the
90
+ right way to restore one of these if it goes missing.
@@ -0,0 +1,26 @@
1
+ ### Native chrome from your views
2
+
3
+ The ERB helpers (`native_app?`, `everywhere_nav_button`, `everywhere_fab`, …) ship with the
4
+ Rails engine, so they aren't available here. The markup contract behind them is public,
5
+ though, and the shell watches for it on every Turbo visit — write it directly:
6
+
7
+ - `data-everywhere-nav-button` (+ `-nav-title`, `-nav-icon`, `-nav-icon-ios`,
8
+ `-nav-icon-android`, `-nav-style`, `side`) — lifts a link or submit button into the top
9
+ navigation bar. Tapping the native control clicks your element.
10
+ - `data-everywhere-nav-menu` with `data-everywhere-menu-item` children (+ `-menu-title`,
11
+ `-menu-style`) — a native pull-down menu.
12
+ - `data-everywhere-menu` + `-menu-trigger` + `-menu-items` — an in-content action sheet.
13
+ - `data-everywhere-haptic` — a tap haptic.
14
+ - `data-everywhere-biometric-lock` (+ `-content`, `-locked`, `-unlock`, `-reason`,
15
+ `-passcode`) — the Face ID / Touch ID gate.
16
+ - `<meta name="everywhere:badge">` and `<meta name="everywhere:tab-badge">` (JSON
17
+ `{path, count}`) — app-icon and tab badges, CSP-safe and correct on first paint.
18
+
19
+ Detect the shell server-side from the User-Agent: the shells prepend
20
+ `RubyEverywhere/<version> (<os>)` to Hotwire Native's own marker. Client-side, use
21
+ `Everywhere.platform` / `Everywhere.native`.
22
+
23
+ `public/native.css`, served via `Rack::Static`, carries the styling helpers — including
24
+ `.everywhere-titlebar-inset` for `window.title_bar: overlay`. Hanami's CSP forbids inline
25
+ scripts, so every bit of this has to be attributes and external files; that's the same reason
26
+ the markup contract exists in the first place.
@@ -0,0 +1,10 @@
1
+ - `native_boot.rb` — the packaged-app entry point. Keep it thin and keep it at the app root.
2
+ - `public/bridge.js` and `public/native.css` — the bridge, vendored and refreshed on each
3
+ `every install`. Serve `public/` via `Rack::Static` and load `/bridge.js` as an **external**
4
+ module; Hanami's CSP forbids inline scripts.
5
+
6
+ Rack apps vary too much for the CLI to auto-edit their boot files, so the rest is by hand and
7
+ `every install` prints it rather than guessing: override `DATABASE_URL` from
8
+ `ENV["NATIVE_STORAGE_DIR"]` in `config.ru` when `NATIVE_PACKAGED` is set, and migrate before
9
+ Hanami finalizes (`Hanami.prepare`, run the ROM migrations, then `require "hanami/boot"`).
10
+ Both only matter for on-device packaging.
@@ -0,0 +1,98 @@
1
+ ### Native chrome from your views
2
+
3
+ `Everywhere::Engine` mixes a set of helpers into every view. **Prefer these over hand-written
4
+ markup or JavaScript.** They render a real link or button that works in a browser, and carry
5
+ the `data-everywhere-*` attributes the shell lifts into native chrome — tapping the native
6
+ control just clicks the element it mirrors, so behavior is defined once and CSP is never an
7
+ issue.
8
+
9
+ Icons are per-platform, like tabs: `icon:` is the shared fallback, `icons: { ios:, android: }`
10
+ names an SF Symbol and a Material Symbol respectively.
11
+
12
+ **Where am I?** Pick the narrowest predicate that's true of what you're branching on.
13
+
14
+ | Helper | |
15
+ | --- | --- |
16
+ | `native_app?` | any shell — iOS, Android, or desktop. "Not a browser tab." |
17
+ | `mobile_app?` | a phone: safe-area insets, the native tab bar, biometrics, the OAuth handoff |
18
+ | `desktop_app?` | the desktop window: title bar, menu bar, pointer |
19
+ | `native_platform` | `:ios`, `:android`, `:desktop`, or `nil` in a browser |
20
+ | `mobile_platform` | `:ios`, `:android`, or `nil` (including on desktop) |
21
+ | `native_version` | the shell's version as a `Gem::Version` — gate features that need a newer shell |
22
+
23
+ Detection is by User-Agent, so it's correct on the server before the JS bridge has booted.
24
+ Hiding a web nav in favor of native chrome is usually `mobile_app?`, not `native_app?` — the
25
+ desktop shell has no native navigation to replace it with.
26
+
27
+ **Navigation bar.** Renders in the page for browsers; the shell hides the in-page copy and
28
+ puts it in the top bar.
29
+
30
+ ```erb
31
+ <%= everywhere_nav_button "New", new_note_path, icons: { ios: "plus", android: "add" } %>
32
+ <%= everywhere_nav_button "Back", notes_path, side: "left" %>
33
+
34
+ <%= form_with model: @note do |f| %>
35
+ <%= everywhere_submit_button "Save" %> <%# mirrors the form's submit into the nav bar %>
36
+ <% end %>
37
+
38
+ <%= everywhere_nav_menu do %> <%# ⋯ pull-down; a native UIMenu in the shell %>
39
+ <%= everywhere_menu_item "Share", share_path, icons: { ios: "square.and.arrow.up" } %>
40
+ <%= everywhere_menu_item "Delete", note_path(@note), method: :delete, style: "destructive" %>
41
+ <% end %>
42
+ ```
43
+
44
+ `everywhere_nav_button` takes `side:` (`"right"` / `"left"`), `style:` (`"done"`,
45
+ `"destructive"`), and `type: :submit`. `everywhere_menu_item` is a link by default; `method:`
46
+ makes it a Turbo method link, `type: :submit` with `form:` submits a form, and
47
+ `style: "destructive"` tints it natively.
48
+
49
+ **In-content controls.**
50
+
51
+ ```erb
52
+ <%# floating action button — fixed, safe-area-aware, tap haptic in the shell %>
53
+ <%= everywhere_fab new_note_path, icon: :plus, label: "New note" %>
54
+ <%= everywhere_fab compose_path, icon: :pencil, label: "Compose", extended: true %>
55
+
56
+ <%# action sheet: native sheet in the shell, inline menu in a browser %>
57
+ <%= everywhere_menu "Options" do %>
58
+ <%= everywhere_menu_item "Edit", edit_post_path(@post) %>
59
+ <%= everywhere_menu_item "Delete", post_path(@post), method: :delete, style: "destructive" %>
60
+ <% end %>
61
+ ```
62
+
63
+ `everywhere_fab` accepts `icon:` (a built-in line glyph), `label:` (accessible name, plus a
64
+ visible pill with `extended: true`), `side:`, and `haptic:` — or a block for custom content.
65
+
66
+ **Badges.** Server-rendered meta tags the bridge applies on every Turbo visit; `0` clears.
67
+
68
+ ```erb
69
+ <%= everywhere_badge Current.user.unread_count %> <%# app icon %>
70
+ <%= everywhere_tab_badge "/inbox", Current.user.unread_count %> <%# a tab, by its everywhere.yml path %>
71
+ ```
72
+
73
+ **Biometric gate.** Keeps sensitive markup hidden until Face ID / Touch ID passes, and only
74
+ when the user has turned the device-local lock on. Browsers and the desktop shell render the
75
+ content plainly. Needs `biometrics` under `permissions:` in `config/everywhere.yml`.
76
+
77
+ ```erb
78
+ <%= everywhere_biometric_lock reason: "Unlock account settings" do %>
79
+ <%# profile, password, sessions… %>
80
+ <% end %>
81
+
82
+ <div data-everywhere-biometric-toggle-row hidden>
83
+ <label>Require Face ID <%= everywhere_biometric_toggle %></label>
84
+ </div>
85
+ ```
86
+
87
+ **Auth redirects.** In a controller, route post-sign-in redirects through the reset page so a
88
+ phone shell rebuilds its web views and re-fetches its tab bar:
89
+
90
+ ```ruby
91
+ redirect_to everywhere_auth_redirect(after_authentication_url)
92
+ ```
93
+
94
+ **Tab bar, per request.** `Everywhere.filter_tabs { |tabs, request| … }` in an initializer
95
+ varies the mobile tab bar — it shares the app's session; return `[]` to hide the bar.
96
+
97
+ Styling helpers live in `everywhere/native.css`, including `.everywhere-titlebar-inset` for
98
+ `window.title_bar: overlay`.
@@ -0,0 +1,16 @@
1
+ - `native_boot.rb` — the packaged-app entry point. Keep it thin and keep it at the app root.
2
+ - `config/initializers/everywhere.rb` — packaged-only public-path and session-store setup.
3
+ - `config/boot.rb` — bootsnap guarded with `unless ENV["NATIVE_PACKAGED"]` (its cache dir is
4
+ read-only when packaged).
5
+ - `config/environments/production.rb` — `force_ssl` and `assume_ssl` are `false`, because a
6
+ packaged app's webview talks plain HTTP to `127.0.0.1`.
7
+ - `config/puma.rb` — keep-alives off when packaged; WebKit reuses connections Puma has closed.
8
+ - `config/database.yml` — the production SQLite path reads
9
+ `<%= ENV.fetch("NATIVE_STORAGE_DIR", "storage") %>`. Any database you add should use the same
10
+ lookup.
11
+ - `app/javascript/application.js` — imports the bridge and exposes it as `window.Everywhere`.
12
+
13
+ One rule that matters in every mode: the bridge is served and importmap-pinned by
14
+ `Everywhere::Engine` straight from the gem, so it updates with `bundle update ruby_everywhere`.
15
+ Don't vendor it into `vendor/javascript/` or pin it in `config/importmap.rb` — a local copy
16
+ shadows the engine's and freezes the app on an old bridge.
@@ -0,0 +1,24 @@
1
+ ### Native chrome from your views
2
+
3
+ The ERB helpers (`native_app?`, `everywhere_nav_button`, `everywhere_fab`, …) ship with the
4
+ Rails engine, so they aren't available here. The markup contract behind them is public,
5
+ though, and the shell watches for it on every Turbo visit — write it directly:
6
+
7
+ - `data-everywhere-nav-button` (+ `-nav-title`, `-nav-icon`, `-nav-icon-ios`,
8
+ `-nav-icon-android`, `-nav-style`, `side`) — lifts a link or submit button into the top
9
+ navigation bar. Tapping the native control clicks your element.
10
+ - `data-everywhere-nav-menu` with `data-everywhere-menu-item` children (+ `-menu-title`,
11
+ `-menu-style`) — a native pull-down menu.
12
+ - `data-everywhere-menu` + `-menu-trigger` + `-menu-items` — an in-content action sheet.
13
+ - `data-everywhere-haptic` — a tap haptic.
14
+ - `data-everywhere-biometric-lock` (+ `-content`, `-locked`, `-unlock`, `-reason`,
15
+ `-passcode`) — the Face ID / Touch ID gate.
16
+ - `<meta name="everywhere:badge">` and `<meta name="everywhere:tab-badge">` (JSON
17
+ `{path, count}`) — app-icon and tab badges, CSP-safe and correct on first paint.
18
+
19
+ Detect the shell server-side from the User-Agent: the shells prepend
20
+ `RubyEverywhere/<version> (<os>)` to Hotwire Native's own marker. Client-side, use
21
+ `Everywhere.platform` / `Everywhere.native`.
22
+
23
+ `public/native.css` carries the styling helpers, including `.everywhere-titlebar-inset` for
24
+ `window.title_bar: overlay`.
@@ -0,0 +1,9 @@
1
+ - `native_boot.rb` — the packaged-app entry point. Keep it thin and keep it at the app root.
2
+ - `public/bridge.js` and `public/native.css` — the bridge, vendored and refreshed on each
3
+ `every install`. Load it with `<script type="module" src="/bridge.js"></script>`.
4
+
5
+ Rack apps vary too much for the CLI to auto-edit their boot files, so the rest is by hand and
6
+ `every install` prints it rather than guessing: point the production SQLite path at
7
+ `ENV.fetch("NATIVE_STORAGE_DIR", "storage")`, and call
8
+ `Everywhere::Database.prepare!(__dir__)` from `config.ru` after loading the app. Both only
9
+ matter for on-device packaging.
@@ -0,0 +1,12 @@
1
+ The app itself is compiled into the shipped binary and runs on the user's machine, rather than
2
+ the shells pointing at a deployed site. Shipping a change means cutting a new build and
3
+ release.
4
+
5
+ That trade brings constraints the docs cover properly — a read-only application filesystem
6
+ with a separate writable data directory, SQLite only, desktop only — so read
7
+ <https://rubyeverywhere.com/docs> before changing anything that writes to disk, boots the app,
8
+ or touches `native_boot.rb`. The short version: anything written at runtime belongs under
9
+ `ENV.fetch("NATIVE_STORAGE_DIR", "storage")`, and `ENV["NATIVE_PACKAGED"]` is the guard for
10
+ packaged-only behavior.
11
+
12
+ Everything below applies the same way in either mode.
@@ -0,0 +1,14 @@
1
+ The native shells are a thin wrapper around the deployed site at `remote.url` in
2
+ `config/everywhere.yml`. Nothing about the app is compiled into them.
3
+
4
+ **So you are working on an ordinary web app.** Ship a change by deploying as you always have,
5
+ and users see it on their next launch — no rebuild, no app-store review. Normal databases,
6
+ background jobs, and hosting all apply, unchanged.
7
+
8
+ Only three things are native-specific, and they're all covered below: what
9
+ `config/everywhere.yml` declares, the view markup that becomes native chrome, and the
10
+ `Everywhere` JavaScript API. Of those, tab and path-rule changes deploy with the app; the rest
11
+ of `config/everywhere.yml` needs a new build of the shells.
12
+
13
+ Remote is also the only mode mobile supports — iOS and Android are always a shell around a
14
+ deployed app.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_everywhere
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrea Fomera
@@ -65,8 +65,13 @@ dependencies:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
67
  version: '0.15'
68
- description: Packages a Rails (and one day Hanami) app into a single-file binary with
69
- Tebako and wraps it in a Tauri desktop shell as a double-clickable app.
68
+ description: RubyEverywhere gives a Rails (or Sinatra, or Hanami) app a native life
69
+ on desktop, iOS, and Android. Desktop is a Tauri shell in local mode the whole
70
+ app is pressed into a single-file binary with Tebako and runs on-device with no
71
+ Ruby install; in remote mode the shell wraps your deployed app. iOS and Android
72
+ are Hotwire Native shells with a real tab bar, nav-bar buttons, menus, notifications,
73
+ haptics, biometrics, and deep links. One config file, one CLI, one JavaScript bridge,
74
+ with the web app staying the source of truth.
70
75
  email:
71
76
  - andrea.fomera@gmail.com
72
77
  executables:
@@ -86,6 +91,7 @@ files:
86
91
  - exe/every
87
92
  - exe/rbe
88
93
  - lib/everywhere.rb
94
+ - lib/everywhere/agents_guide.rb
89
95
  - lib/everywhere/android_resources.rb
90
96
  - lib/everywhere/android_sdk.rb
91
97
  - lib/everywhere/asset_catalog.rb
@@ -151,6 +157,15 @@ files:
151
157
  - lib/everywhere/update_manifest.rb
152
158
  - lib/everywhere/version.rb
153
159
  - lib/ruby_everywhere.rb
160
+ - support/agents/AGENTS.md
161
+ - support/agents/frameworks/hanami-views.md
162
+ - support/agents/frameworks/hanami.md
163
+ - support/agents/frameworks/rails-views.md
164
+ - support/agents/frameworks/rails.md
165
+ - support/agents/frameworks/sinatra-views.md
166
+ - support/agents/frameworks/sinatra.md
167
+ - support/agents/modes/local.md
168
+ - support/agents/modes/remote.md
154
169
  - support/desktop/README.md
155
170
  - support/desktop/splash/index.html
156
171
  - support/desktop/src-tauri/Cargo.lock
@@ -258,7 +273,11 @@ files:
258
273
  homepage: https://rubyeverywhere.com
259
274
  licenses:
260
275
  - MIT
261
- metadata: {}
276
+ metadata:
277
+ homepage_uri: https://rubyeverywhere.com
278
+ source_code_uri: https://github.com/RubyEverywhere/gem
279
+ bug_tracker_uri: https://github.com/RubyEverywhere/gem/issues
280
+ rubygems_mfa_required: 'true'
262
281
  rdoc_options: []
263
282
  require_paths:
264
283
  - lib
@@ -275,6 +294,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
275
294
  requirements: []
276
295
  rubygems_version: 4.0.16
277
296
  specification_version: 4
278
- summary: RubyEverywhere ship desktop apps from the Ruby web apps you already know
279
- how to build
297
+ summary: Ship desktop and mobile apps from the Ruby web apps you already know how
298
+ to build
280
299
  test_files: []