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 TurboDesktop global — JavaScript API for native features
  • Bridge message passingsendBridgeMessage() 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

  1. User clicks a link in the app
  2. Turbo Drive fires turbo:before-visit
  3. turbo-desktop.js intercepts and calls handle_visit_proposal on the Rust side
  4. Rust checks the URL against path configuration rules
  5. 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