A pure-Ruby desktop shell: a native window over the operating system's webview (WebView2 / WKWebView / WebKitGTK, via the webview C library), with a Ruby⇄JS operation bridge. A lightweight, Tauri-shaped way to ship a web frontend as a desktop app — in Ruby.
Named for Cyrano's Roxane.
Roxane is the host and data plane — a window, the system webview, an
invoke/emit bridge, and a loopback asset server. It serves whatever frontend
you give it and is agnostic to look-and-feel (no CSS, no widgets, no layout
opinions). The same frontend can run in the desktop shell and in a hosted browser.
Reach for Roxane when you want a real desktop window around a web UI, and Ruby owns the logic:
- Ship a web frontend as a desktop app without Electron’s bulk — your UI can be plain HTML, or a Vite/React/Svelte/etc. build you already have
- Ruby on the inside, JS on the glass — long work (AI calls, imports, jobs)
runs in Ruby handlers off the UI thread; the page stays responsive via
invoke/emit - One frontend, two shells — develop in the browser against a dev server
(
load), ship the same assets in the native window (serve) - Local tools and studios — editors, dashboards, creative apps, ops utilities where Ruby is already home and you want a polished window, not a terminal-only UX
Not a fit if you need native OS widgets, deep App Store chrome, or a batteries- included UI framework — Roxane deliberately does none of that. It’s the thin shell under your frontend.
gem install roxane
# or in a Gemfile:
# gem "roxane", ">= 0.0.4"Bundler picks the matching platform gem when one exists (vendored libwebview).
You still need the OS webview runtime (WebKitGTK on Linux, WebView2 on Windows,
WKWebView on macOS).
require "roxane"
win = Roxane::Window.new(title: "Hello", size: [900, 600])
# JS → Ruby: await window.roxane.invoke("greet", "world")
win.on("greet") { |name| "Hello, #{name}!" }
# Ruby → JS: window.roxane.on("tick", ({ at }) => ...)
win.emit("tick", { at: Time.now.to_i })
# Full-content zoom (WebKitGTK today; no-op where the engine binding is absent)
win.zoom = 1.2
win.serve(File.expand_path("ui")) # serve a built frontend bundle, or:
# win.load("http://localhost:5173") # point at a dev server, or:
# win.html("<!doctype html>…") # inline HTML
win.runInvocation handlers run off the UI thread, and emit/eval are safe from any
thread — so long-running work (e.g. an AI call) never freezes the window.
The webview C library (libwebview) must be available at runtime. Precompiled
platform gems vendor it, so you only need the system webview runtime.
Otherwise set ROXANE_LIBWEBVIEW to a libwebview path, or have it on the
loader path.
Roxane ships a prebuilt libwebview per platform (the nokogiri model) so users
need no toolchain. CI builds and publishes to RubyGems
on each v* tag for:
| Platform gem | Runner |
|---|---|
x86_64-linux |
Ubuntu |
aarch64-linux |
Ubuntu ARM |
arm64-darwin |
macOS Apple Silicon |
x86_64-darwin |
macOS Intel |
x64-mingw-ucrt |
Windows |
| ruby (no binary) | source gem |
Local Linux packaging (Docker):
rake vendor:linux # build libwebview into vendor/x86_64-linux/
rake gem:linux # build roxane-<ver>-x86_64-linux.gem (vendoring the lib)
rake verify:linux # install that gem in a clean container and smoke-test itMulti-platform gems are produced by GitHub Actions (.github/workflows/build-gems.yml),
not these rake tasks.
./run.sh # Docker: Ruby + libwebview + headless Xvfb → full suite
rake test # host: unit tests only (the asset server); the windowed
# smoke test self-skips unless ROXANE_INTEGRATION=1On GTK3, upstream libwebview implements WEBVIEW_HINT_NONE with
gtk_window_resize. Under Wayland that does not set the initial xdg
geometry, so floating compositors (e.g. Hyprland) often map the window at a
tiny 200×200 square; GNOME hides the same bug. Roxane compensates on Linux by
calling gtk_window_set_default_size and briefly pinning a size-request through
first map (then releasing it so the window stays resizable).
Early but working (0.0.4). The runtime (window + system webview + invoke/emit bridge + loopback asset server), native zoom on WebKitGTK, and Wayland initial sizing are in place. Platform gems publish to RubyGems on version tags.
Next: richer window options (menus, frameless), and zoom bindings for macOS/Windows engines.
MIT