Skip to content

Repository files navigation

Roxane

build-gems

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.

What it is (and isn't)

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.

When to use it

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.

Install

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).

Example

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.run

Invocation 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.

Requirements

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.

Packaging (precompiled platform gems)

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 it

Multi-platform gems are produced by GitHub Actions (.github/workflows/build-gems.yml), not these rake tasks.

Testing

./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=1

Linux / Wayland note (window size)

On 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).

Status

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.

License

MIT

About

Turn a web UI into a native desktop app — in Ruby. Thin window + system webview, no Electron.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages