Architecture - aguspe/turbo_desktop GitHub Wiki
Architecture
Turbo Desktop follows a three-layer architecture that mirrors the Hotwire Native pattern (turbo-ios / turbo-android):
┌──────────────┐ ┌──────────────┐ ┌──────────────────┐
│ Rails Server │ ──▶ │ WebView │ ──▶ │ Tauri / Rust │
│ HTML + Turbo │ │ turbo- │ │ Windows, menus, │
│ Drive │ │ desktop.js │ │ OS APIs │
└──────────────┘ └──────────────┘ └──────────────────┘
Layer 1: Rails Server
Your existing Rails app is the single source of truth. It serves HTML with Turbo Drive — the same views you already have. No separate API needed.
The turbo_desktop-rails gem adds:
- Detection helpers (
turbo_desktop_app?,turbo_desktop_platform) - View helpers (
turbo_desktop_only,turbo_desktop_bridge) - Path configuration endpoint (
/turbo-desktop/path-configuration.json)
Layer 2: WebView + turbo-desktop.js
The WebView renders your Rails HTML. turbo-desktop.js is injected into every page and acts as the bridge:
- Intercepts Turbo Drive visits — sends navigation proposals to Rust
- Provides the
TurboDesktopglobal — JavaScript API for native features - Bridge message passing —
sendBridgeMessage()connects web to native - Offline detection — monitors connectivity and shows/hides offline UI
Layer 3: Tauri / Rust Shell
The native shell handles everything the web can't do:
| Module | Responsibility |
|---|---|
main.rs |
App entry point, plugin initialization, JS injection |
navigation.rs |
Path configuration routing (default, modal, new_window, etc.) |
config.rs |
Fetches and stores path configuration from Rails |
bridge.rs |
Handles bridge messages (notifications, menus, file pickers) |
window.rs |
Window configuration and management |
menu.rs |
Native menu bar |
tray.rs |
System tray |
shell_bridge.rs |
Child process spawning |
fs_bridge.rs |
File system access |
updater_bridge.rs |
App update mechanism |
Request Flow
- User clicks a link in the app
- Turbo Drive fires
turbo:before-visit turbo-desktop.jsintercepts and callshandle_visit_proposalon the Rust side- Rust checks the URL against path configuration rules
- Based on the matching rule's
presentation:- default → navigate the current window
- modal → open a new 800x600 overlay window
- new_window → open a new 1200x800 window
- replace → navigate with no back button
- native → emit an event for Rust to handle
- none → do nothing
Comparison with Mobile
| 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 |
Why Tauri?
- Tiny binaries — uses the OS WebView (no bundled Chromium)
- Rust backend — safe, fast, cross-platform
- Active ecosystem — mature plugin system for OS integrations
- Cross-platform — macOS, Windows, and Linux from one codebase