Turbo Desktop
Turbo Native for Desktop — wrap your Rails app in a native macOS / Windows / Linux shell
Features • Architecture • Quick Start • Path Config • Bridge • Rails Gem • Comparison • Docs
The Problem
Rails developers already have Hotwire Native (turbo-ios and turbo-android) to wrap their web apps in native mobile shells. But there has been nothing for desktop.
Turbo Desktop fills this gap. It gives you a thin, native desktop shell powered by Tauri 2 that treats your Rails app as the single source of truth — the same pattern you already know from Hotwire Native, but for the desktop.
Features
- No new UI framework — your existing Rails views, Turbo Frames, and Stimulus controllers just work
- Native when you need it — notifications, file pickers, menus, and keyboard shortcuts via Bridge Components
- Tiny binary — Tauri uses the OS WebView, no bundled Chromium. Ship a ~5-10 MB app
- Path configuration — JSON-based routing rules (same concept as turbo-ios / turbo-android)
- Bridge components — web-to-native communication via Stimulus controllers
-
Rails gem —
turbo_desktop-railsgives your Rails app desktop shell awareness -
CLI scaffolding —
npx turbo-desktop new myappto get started fast
Architecture
┌──────────────┐ ┌──────────────┐ ┌──────────────────┐
│ Rails Server │ ──▶ │ WebView │ ──▶ │ Tauri / Rust │
│ HTML + Turbo │ │ turbo- │ │ Windows, menus, │
│ Drive │ │ desktop.js │ │ OS APIs │
└──────────────┘ └──────────────┘ └──────────────────┘
Three layers that mirror the Hotwire Native pattern:
- Rails Server — your existing app serves HTML with Turbo Drive
-
WebView —
turbo-desktop.jsintercepts Turbo visits and bridges to native - Tauri Shell — Rust handles window management, path config routing, and OS APIs
Quick Start
1. Clone and install dependencies
git clone https://github.com/aguspe/turbo_desktop.git
cd turbo_desktop
cargo install tauri-cli
npm install2. Configure your Rails server URL
Edit turbo-desktop.toml:
[app]
server_url = "http://localhost:3000"
app_name = "My App"
[path_configuration]
url = "http://localhost:3000/turbo-desktop/path-configuration.json"3. Add the Rails gem
# Gemfile
gem "turbo_desktop-rails"bundle install
rails generate turbo_desktop:install4. Serve path configuration from Rails
# config/routes.rb
get "/turbo-desktop/path-configuration", to: "turbo_desktop#path_configuration"5. Run the desktop app
# Start your Rails server
bin/rails server
# Start the Tauri desktop app
cargo tauri devPath Configuration
The path configuration is a JSON file that maps URL patterns to presentation rules — the same concept from turbo-ios and turbo-android.
{
"settings": {
"screenshots_enabled": true,
"pull_to_refresh_enabled": false
},
"rules": [
{
"patterns": ["/"],
"properties": { "presentation": "default" }
},
{
"patterns": ["/new$", "/edit$"],
"properties": { "presentation": "modal", "title": "Edit" }
},
{
"patterns": ["/reports/"],
"properties": { "presentation": "new_window" }
},
{
"patterns": ["/settings"],
"properties": { "presentation": "native" }
}
]
}| Presentation | Behavior |
|---|---|
default |
Navigate in the current window (Turbo Drive handles it) |
modal |
Open the URL in a modal-style window (800×600) |
new_window |
Open the URL in a full separate window (1200×800) |
replace |
Replace the current page with no back-navigation |
native |
Emit a native-screen-requested event for Rust UI |
none |
Do nothing — handled entirely by a Bridge Component |
Bridge Components
The Bridge is the desktop equivalent of Strada. It lets your web components talk to native OS features through structured message passing.
Built-in Components
| Component | Description |
|---|---|
notification |
Show native OS notifications |
menu-item |
Register items in the native menu bar |
file-picker |
Open native file-open/save dialogs |
badge |
Set the dock/taskbar badge count |
shortcut |
Register global keyboard shortcuts |
JavaScript Example
import { Controller } from "@hotwired/stimulus"
export default class extends TurboDesktop.stimulusBridge(Controller, "notification") {
connect() {
super.connect()
this.sendBridge("connect", { title: "My App" })
}
notify(event) {
this.sendBridge("connect", {
title: "New Message",
body: event.target.dataset.body
})
}
receiveBridge(message) {
console.log("Native says:", message)
}
}Rails View Helpers
<%# Attach bridge data attributes to any element %>
<%= tag.button "Export PDF",
**turbo_desktop_bridge("menu-item",
title: "Export PDF",
shortcut: "Cmd+E"
) %>Rails Gem
The turbo_desktop-rails gem gives your Rails app awareness of the desktop shell.
| Helper | Description |
|---|---|
turbo_desktop_app? |
Returns true if request comes from Turbo Desktop |
turbo_desktop_platform |
Returns "macos", "windows", "linux", or nil
|
turbo_desktop_arch |
Returns "aarch64", "x86_64", or nil
|
turbo_desktop_only { } |
Renders block only inside the desktop app |
turbo_web_only { } |
Renders block only for web (non-desktop) users |
turbo_desktop_bridge(component, **opts) |
Outputs bridge data attributes |
Comparison
| Concept | turbo-ios | turbo-android | Turbo Desktop |
|---|---|---|---|
| Shell runtime | WKWebView (Swift) | WebView (Kotlin) | Tauri WebView (Rust) |
| Path configuration | JSON, last-match-wins | JSON, last-match-wins | JSON, last-match-wins |
| Bridge / native comms | Strada | Strada | BridgeComponent |
| JS injection | WKUserScript | evaluateJavascript | on_page_load + eval |
| Rails gem | turbo-rails | turbo-rails | turbo_desktop-rails |
| Binary size | System WebKit | ~20 MB | ~5-10 MB |
| Platforms | iOS, iPadOS | Android | macOS, Windows, Linux |
Project Structure
turbo_desktop/
├── src/ # JavaScript (turbo-desktop.js)
├── src-tauri/ # Rust / Tauri shell
│ └── src/
│ ├── main.rs # App entry point
│ ├── navigation.rs # Visit proposals & path config routing
│ ├── bridge.rs # Bridge component handlers
│ └── window.rs # Window management
├── turbo_desktop-rails/ # Rails gem
├── cli/ # CLI scaffolding tool
├── templates/ # Project templates
├── test/ # Tests
└── docs/ # Documentation
License
MIT — see LICENSE for details.
Built with Tauri, Hotwire, and Ruby on Rails.