Desktop Client

The Diminuendo desktop client (@igentai/dim-desktop) is a native desktop application built with Tauri v2 and React. It provides the same user interface as the web client but runs as a native window with the WebSocket connection managed by a Rust backend process. This architecture delivers lower latency, native window management, system tray integration, and direct local filesystem access for Chronicle workspace synchronization.

Tech Stack

TechnologyVersionPurpose
Tauriv2 (@tauri-apps/cli ^2.2)Native desktop shell
@tauri-apps/api^2.2Frontend IPC bindings
React19UI framework
TypeScript5.xType safety
Tailwind CSSv4Styling
Effect^3.12Gateway adapter typed effects
Zustand5State management
Diminuendo Rust SDKlocalWebSocket client in the Rust backend

Architecture

Unlike the web client, the desktop client does not hold a WebSocket connection from the renderer process. Instead, the Tauri Rust backend owns the connection:
Tauri Window (webview)
  ├── React Components (from @igentai/dim-shared)
  ├── Zustand Stores (from @igentai/dim-shared)
  ├── TauriGatewayAdapter
  │     ├── invoke() → Rust #[tauri::command] handlers
  │     └── listen("gateway-event") ← Rust app_handle.emit()

Tauri Rust Backend (src-tauri/)
  ├── DiminuendoClient (Rust SDK)
  │     └── WebSocket → Gateway
  ├── tokio event loop
  │     └── event receiver → emit("gateway-event")
  └── Tauri managed state (Arc<Mutex<DiminuendoClient>>)
The Rust backend spawns a tokio task that reads from the Rust SDK’s mpsc::UnboundedReceiver<ServerEvent> and emits each event to the frontend via Tauri’s event system. The frontend’s TauriGatewayAdapter listens for these events and forwards them into the shared Zustand stores.

Data Flow

Command Path (Frontend to Gateway)

1

User Action

The user submits a message through the chat input.
2

Adapter Dispatch

useChat() calls adapter.runTurn() on the TauriGatewayAdapter, which calls invoke("run_turn", { sessionId, text }).
3

Tauri IPC

Tauri’s IPC bridge serializes the arguments and delivers them to the Rust process.
4

Rust Command

The #[tauri::command] run_turn handler retrieves the DiminuendoClient from Tauri’s managed state and calls client.run_turn().
5

WebSocket

The Rust SDK serializes the message as JSON and sends it over the WebSocket to the gateway.

Event Path (Gateway to Frontend)

1

Gateway Event

The gateway publishes an event to the session topic.
2

WebSocket

The Rust SDK’s background tokio task receives the event from the WebSocket and sends it through the mpsc channel.
3

Event Emission

The event receiver task calls app_handle.emit("gateway-event", &event), which serializes the event and sends it to the frontend webview.
4

Adapter Stream

The TauriGatewayAdapter’s listen("gateway-event") callback receives the event and pushes it into the Effect Stream.Stream.
5

Store Update

The useGatewayConnection hook routes the event to the appropriate Zustand store. React components re-render with the updated state.

Advantages over the Web Client

Lower Latency

The Rust backend holds the WebSocket connection natively via tokio-tungstenite. There is no browser WebSocket implementation overhead, no JavaScript event loop contention for I/O processing, and no garbage collection pauses during high-throughput event streams.

Native Window Management

Full control over window chrome, title bar, resizing behavior, and multi-window support via Tauri’s window management API. The application can implement custom title bars, frameless windows, and platform-specific window behaviors.

System Tray

Background presence via system tray icon with notification badges. The application can run minimized to the tray and surface notifications when agent turns complete or questions are requested.

Local File Access

Direct filesystem access for Chronicle local-sync integration. The desktop client can synchronize agent workspace files to local directories, enabling users to open and edit files in their preferred IDE.

Chronicle Local-Sync Integration

The desktop client integrates with Chronicle’s local-sync mode, which replaces FUSE/NFS with real APFS files synchronized via FSEvents. Tauri IPC commands provide the interface:
CommandDescription
start_syncBegin synchronizing a session’s workspace to a local directory
stop_syncStop synchronization for a session
sync_statusQuery the current sync state (active, paused, error)
resolve_conflictResolve a file conflict when both local and remote changes exist
// Start syncing a session workspace to ~/workspaces/session-123/
await invoke("start_sync", {
  sessionId: "session-123",
  localDir: "/Users/dev/workspaces/session-123",
})

// Check sync status
const status = await invoke("sync_status", { sessionId: "session-123" })
// { state: "active", filesTracked: 42, lastSyncAt: 1709312400000 }

// Stop syncing
await invoke("stop_sync", { sessionId: "session-123" })
Chronicle local-sync uses the notify crate (v7) with RecommendedWatcher for FSEvents on macOS. File changes are debounced and echo-suppressed to prevent infinite sync loops when the materializer writes files that the watcher would otherwise detect as local changes.

Development

Start the desktop client in development mode:
cd clients
npm install
npm run dev:desktop
This starts the Tauri development window with hot module replacement for the frontend and automatic Rust recompilation for the backend. The Rust backend compiles with the local-sync feature enabled by default.
The desktop client’s Vite config is shared with the web client but excludes the WebSocket-related code paths. Tauri’s __TAURI__ global is used for platform detection at runtime.

Build

cd clients
npm run build:desktop
Produces platform-specific installers:
  • macOS: .dmg and .app bundle
  • Windows: .msi installer and .exe
  • Linux: .deb, .rpm, and .AppImage

Prerequisites

  • Rust toolchain (stable)
  • Tauri CLI: cargo install tauri-cli
  • Platform-specific dependencies (see Tauri prerequisites)
  • A running Diminuendo gateway instance for development