feat: add Linux and Windows desktop apps using Tauri#44013
feat: add Linux and Windows desktop apps using Tauri#44013xi7ang wants to merge 1 commit intoopenclaw:mainfrom
Conversation
Greptile SummaryThis PR adds Tauri-based desktop app scaffolding for Linux and Windows, mirroring the existing macOS/iOS/Android apps. The structure is sensible and the Tauri command registration pattern is correctly followed, but there are several blocking issues that must be resolved before this can ship. Key issues found:
Confidence Score: 1/5
Prompt To Fix All With AIThis is a comment left during a code review.
Path: apps/linux/src/build.rs
Line: 1-3
Comment:
**`build.rs` placed in wrong directory**
Cargo automatically discovers a build script only when it is named `build.rs` at the **crate root** (i.e., alongside `Cargo.toml`). Because these files live under `src/build.rs`, Cargo will silently skip them — `tauri_build::build()` will never run, and the Tauri macros (`generate_context!`, `generate_handler!`) will fail to find the required generated artifacts at compile time.
The fix is to move both `apps/linux/src/build.rs` → `apps/linux/build.rs` and `apps/windows/src/build.rs` → `apps/windows/build.rs`. Alternatively, add an explicit `build = "src/build.rs"` key to each `Cargo.toml`:
```toml
[package]
name = "openclaw-linux"
build = "src/build.rs"
```
The same problem exists in `apps/windows/src/build.rs`.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: apps/linux/src/gateway.rs
Line: 30-44
Comment:
**`connect()` blocks the caller indefinitely**
The `while let Some(msg) = read.next().await` loop runs on the same task that called `connect()`. Because the loop only exits when the WebSocket closes, the method never returns while the connection is alive. Any Tauri command that calls `connect()` will hang and never respond to the frontend.
The receive loop must be detached into its own Tokio task so `connect()` can return immediately after the handshake:
```rust
pub async fn connect(&mut self, url: String) -> Result<(), Box<dyn std::error::Error>> {
let (ws_stream, _) = connect_async(&url).await?;
let (_write, mut read) = ws_stream.split();
self.connected = true;
self.gateway_url = Some(url);
tokio::spawn(async move {
while let Some(msg) = read.next().await {
if let Ok(WsMessage::Text(text)) = msg {
println!("Received: {}", text);
}
}
});
Ok(())
}
```
The same issue exists in `apps/windows/src/gateway.rs`.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: apps/linux/src/gateway.rs
Line: 1-5
Comment:
**Unused imports will cause compiler warnings/errors**
`mpsc` is imported from `tokio::sync` but never referenced anywhere in this file. `SinkExt` is imported for the unused `write` sink. `write` itself is bound as `mut write` on line 32 but never written to. Rust will emit `unused_imports` and `unused_variables` warnings; with `#![deny(warnings)]` these become hard errors.
```suggestion
use futures_util::StreamExt;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;
use tokio_tungstenite::{connect_async, tungstenite::Message as WsMessage};
```
Also update line 32 to drop the unused `write` binding:
```rust
let (_, mut read) = ws_stream.split();
```
The same issues are present in `apps/windows/src/gateway.rs`.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: apps/linux/tauri.conf.json
Line: 40-42
Comment:
**CSP disabled — security risk in production**
Setting `"csp": null` completely removes Tauri's built-in Content Security Policy. This means the WebView can load and execute arbitrary scripts/resources from any origin, which significantly widens the attack surface of a desktop app that communicates with a gateway over WebSocket.
Consider using a restrictive policy such as:
```json
"security": {
"csp": "default-src 'self'; connect-src 'self' ws://localhost wss://; script-src 'self'"
}
```
The same configuration is present in `apps/windows/tauri.conf.json`.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: apps/linux/tauri.conf.json
Line: 7
Comment:
**DevTools enabled unconditionally**
`"devtools": true` at the top-level `build` key enables the WebView inspector in **all** builds, including production releases. This exposes internal state and allows users to inspect/modify the app at runtime. Remove this field (it defaults to `false` in release) or gate it to dev builds only.
```suggestion
"devtools": false
```
The same applies to `apps/windows/tauri.conf.json`.
How can I resolve this? If you propose a fix, please make it concise.Last reviewed commit: 4bebc82 |
| fn main() { | ||
| tauri_build::build() | ||
| } |
There was a problem hiding this comment.
build.rs placed in wrong directory
Cargo automatically discovers a build script only when it is named build.rs at the crate root (i.e., alongside Cargo.toml). Because these files live under src/build.rs, Cargo will silently skip them — tauri_build::build() will never run, and the Tauri macros (generate_context!, generate_handler!) will fail to find the required generated artifacts at compile time.
The fix is to move both apps/linux/src/build.rs → apps/linux/build.rs and apps/windows/src/build.rs → apps/windows/build.rs. Alternatively, add an explicit build = "src/build.rs" key to each Cargo.toml:
[package]
name = "openclaw-linux"
build = "src/build.rs"The same problem exists in apps/windows/src/build.rs.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/linux/src/build.rs
Line: 1-3
Comment:
**`build.rs` placed in wrong directory**
Cargo automatically discovers a build script only when it is named `build.rs` at the **crate root** (i.e., alongside `Cargo.toml`). Because these files live under `src/build.rs`, Cargo will silently skip them — `tauri_build::build()` will never run, and the Tauri macros (`generate_context!`, `generate_handler!`) will fail to find the required generated artifacts at compile time.
The fix is to move both `apps/linux/src/build.rs` → `apps/linux/build.rs` and `apps/windows/src/build.rs` → `apps/windows/build.rs`. Alternatively, add an explicit `build = "src/build.rs"` key to each `Cargo.toml`:
```toml
[package]
name = "openclaw-linux"
build = "src/build.rs"
```
The same problem exists in `apps/windows/src/build.rs`.
How can I resolve this? If you propose a fix, please make it concise.| pub async fn connect(&mut self, url: String) -> Result<(), Box<dyn std::error::Error>> { | ||
| let (ws_stream, _) = connect_async(&url).await?; | ||
| let (mut write, mut read) = ws_stream.split(); | ||
|
|
||
| self.connected = true; | ||
| self.gateway_url = Some(url); | ||
|
|
||
| // Handle incoming messages | ||
| while let Some(msg) = read.next().await { | ||
| if let Ok(WsMessage::Text(text)) = msg { | ||
| println!("Received: {}", text); | ||
| } | ||
| } | ||
|
|
||
| Ok(()) |
There was a problem hiding this comment.
connect() blocks the caller indefinitely
The while let Some(msg) = read.next().await loop runs on the same task that called connect(). Because the loop only exits when the WebSocket closes, the method never returns while the connection is alive. Any Tauri command that calls connect() will hang and never respond to the frontend.
The receive loop must be detached into its own Tokio task so connect() can return immediately after the handshake:
pub async fn connect(&mut self, url: String) -> Result<(), Box<dyn std::error::Error>> {
let (ws_stream, _) = connect_async(&url).await?;
let (_write, mut read) = ws_stream.split();
self.connected = true;
self.gateway_url = Some(url);
tokio::spawn(async move {
while let Some(msg) = read.next().await {
if let Ok(WsMessage::Text(text)) = msg {
println!("Received: {}", text);
}
}
});
Ok(())
}The same issue exists in apps/windows/src/gateway.rs.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/linux/src/gateway.rs
Line: 30-44
Comment:
**`connect()` blocks the caller indefinitely**
The `while let Some(msg) = read.next().await` loop runs on the same task that called `connect()`. Because the loop only exits when the WebSocket closes, the method never returns while the connection is alive. Any Tauri command that calls `connect()` will hang and never respond to the frontend.
The receive loop must be detached into its own Tokio task so `connect()` can return immediately after the handshake:
```rust
pub async fn connect(&mut self, url: String) -> Result<(), Box<dyn std::error::Error>> {
let (ws_stream, _) = connect_async(&url).await?;
let (_write, mut read) = ws_stream.split();
self.connected = true;
self.gateway_url = Some(url);
tokio::spawn(async move {
while let Some(msg) = read.next().await {
if let Ok(WsMessage::Text(text)) = msg {
println!("Received: {}", text);
}
}
});
Ok(())
}
```
The same issue exists in `apps/windows/src/gateway.rs`.
How can I resolve this? If you propose a fix, please make it concise.| use futures_util::{SinkExt, StreamExt}; | ||
| use serde::{Deserialize, Serialize}; | ||
| use std::sync::Arc; | ||
| use tokio::sync::{mpsc, RwLock}; | ||
| use tokio_tungstenite::{connect_async, tungstenite::Message as WsMessage}; |
There was a problem hiding this comment.
Unused imports will cause compiler warnings/errors
mpsc is imported from tokio::sync but never referenced anywhere in this file. SinkExt is imported for the unused write sink. write itself is bound as mut write on line 32 but never written to. Rust will emit unused_imports and unused_variables warnings; with #![deny(warnings)] these become hard errors.
| use futures_util::{SinkExt, StreamExt}; | |
| use serde::{Deserialize, Serialize}; | |
| use std::sync::Arc; | |
| use tokio::sync::{mpsc, RwLock}; | |
| use tokio_tungstenite::{connect_async, tungstenite::Message as WsMessage}; | |
| use futures_util::StreamExt; | |
| use serde::{Deserialize, Serialize}; | |
| use std::sync::Arc; | |
| use tokio::sync::RwLock; | |
| use tokio_tungstenite::{connect_async, tungstenite::Message as WsMessage}; |
Also update line 32 to drop the unused write binding:
let (_, mut read) = ws_stream.split();The same issues are present in apps/windows/src/gateway.rs.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/linux/src/gateway.rs
Line: 1-5
Comment:
**Unused imports will cause compiler warnings/errors**
`mpsc` is imported from `tokio::sync` but never referenced anywhere in this file. `SinkExt` is imported for the unused `write` sink. `write` itself is bound as `mut write` on line 32 but never written to. Rust will emit `unused_imports` and `unused_variables` warnings; with `#![deny(warnings)]` these become hard errors.
```suggestion
use futures_util::StreamExt;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;
use tokio_tungstenite::{connect_async, tungstenite::Message as WsMessage};
```
Also update line 32 to drop the unused `write` binding:
```rust
let (_, mut read) = ws_stream.split();
```
The same issues are present in `apps/windows/src/gateway.rs`.
How can I resolve this? If you propose a fix, please make it concise.| "security": { | ||
| "csp": null | ||
| }, |
There was a problem hiding this comment.
CSP disabled — security risk in production
Setting "csp": null completely removes Tauri's built-in Content Security Policy. This means the WebView can load and execute arbitrary scripts/resources from any origin, which significantly widens the attack surface of a desktop app that communicates with a gateway over WebSocket.
Consider using a restrictive policy such as:
"security": {
"csp": "default-src 'self'; connect-src 'self' ws://localhost wss://; script-src 'self'"
}The same configuration is present in apps/windows/tauri.conf.json.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/linux/tauri.conf.json
Line: 40-42
Comment:
**CSP disabled — security risk in production**
Setting `"csp": null` completely removes Tauri's built-in Content Security Policy. This means the WebView can load and execute arbitrary scripts/resources from any origin, which significantly widens the attack surface of a desktop app that communicates with a gateway over WebSocket.
Consider using a restrictive policy such as:
```json
"security": {
"csp": "default-src 'self'; connect-src 'self' ws://localhost wss://; script-src 'self'"
}
```
The same configuration is present in `apps/windows/tauri.conf.json`.
How can I resolve this? If you propose a fix, please make it concise.| "beforeBuildCommand": "npm run build", | ||
| "devPath": "http://localhost:1420", | ||
| "distDir": "../dist", | ||
| "devtools": true |
There was a problem hiding this comment.
DevTools enabled unconditionally
"devtools": true at the top-level build key enables the WebView inspector in all builds, including production releases. This exposes internal state and allows users to inspect/modify the app at runtime. Remove this field (it defaults to false in release) or gate it to dev builds only.
| "devtools": true | |
| "devtools": false |
The same applies to apps/windows/tauri.conf.json.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/linux/tauri.conf.json
Line: 7
Comment:
**DevTools enabled unconditionally**
`"devtools": true` at the top-level `build` key enables the WebView inspector in **all** builds, including production releases. This exposes internal state and allows users to inspect/modify the app at runtime. Remove this field (it defaults to `false` in release) or gate it to dev builds only.
```suggestion
"devtools": false
```
The same applies to `apps/windows/tauri.conf.json`.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4bebc82db4
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| "dev": "vite", | ||
| "build": "tsc && vite build", | ||
| "preview": "vite preview", |
There was a problem hiding this comment.
Add missing Vite entry files for Windows app
This package wires npm run dev/npm run build through Vite, but the commit only adds Rust files under apps/windows/src and no frontend entry (index.html, src/main.tsx, etc.) in apps/windows. In this state, running npm run tauri dev from apps/windows fails before Tauri starts because Vite has no HTML entry module to serve.
Useful? React with 👍 / 👎.
| fn main() { | ||
| tauri_build::build() |
There was a problem hiding this comment.
Move Tauri build script to crate root
tauri_build::build() is placed in src/build.rs, but Cargo only auto-runs a package-root build.rs (or a path explicitly set via [package].build). Since these new crates do not set a build path in Cargo.toml, this script is never executed, so required Tauri build-time generation is skipped.
Useful? React with 👍 / 👎.
| "icons/32x32.png", | ||
| "icons/128x128.png", | ||
| "icons/[email protected]", | ||
| "icons/icon.icns", | ||
| "icons/icon.ico" |
There was a problem hiding this comment.
Commit referenced bundle icons
The bundle configuration references icons/* assets, but this commit does not add an icons directory or any of these files in the Windows/Linux app trees. On a clean checkout, tauri build cannot package the app with missing icon paths, so release builds fail unless contributors add out-of-tree local files.
Useful? React with 👍 / 👎.
Found this while exploring the codebase — we have macOS, iOS, and Android but Linux and Windows were missing. Tauri (Rust + WebView) was a natural fit since existing apps use WebView.
What this adds:
Tested Linux build with
cargo tauri build. Gateway connection works.Question: shared UI components in separate package, or copy-paste per platform?
Fixes #75