Add desktop platform support #11

Open
claude-bot wants to merge 7 commits from worktree-desktop-support into main
6 changed files with 2934 additions and 59 deletions

View file

@ -6,7 +6,14 @@ on:
pull_request:
jobs:
check:
fmt:
runs-on: rust-ci
steps:
- uses: actions/checkout@v4
- name: Format check
run: cargo fmt --check
clippy-native:
runs-on: rust-ci
steps:
- uses: actions/checkout@v4
@ -17,7 +24,6 @@ jobs:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
cargo-${{ runner.os }}-
@ -26,30 +32,104 @@ jobs:
uses: actions/cache@v4
with:
path: /root/.cache/sccache
# Not keyed to Cargo.lock: sccache caches individual compiler
# invocations by content hash, so it should accumulate across
# dependency bumps rather than reset like the target/ cache above.
key: sccache-${{ runner.os }}-${{ github.run_id }}
# Keyed to Cargo.lock, not github.run_id: a per-run key never
# gets an exact hit, so actions/cache re-uploads the full
# sccache dir on every single run even when nothing changed.
# This still refreshes whenever dependencies bump.
key: sccache-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
sccache-${{ runner.os }}-
- name: Clippy (native crates)
run: cargo clippy --workspace --exclude feedsignal-web --all-targets -- -D warnings
clippy-server:
runs-on: rust-ci
steps:
- uses: actions/checkout@v4
- name: Cache cargo registry and build artifacts
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
cargo-${{ runner.os }}-
- name: Cache sccache compilation objects
uses: actions/cache@v4
with:
path: /root/.cache/sccache
key: sccache-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
sccache-${{ runner.os }}-
- name: Clippy (web crate, server)
run: cargo clippy -p feedsignal-web --no-default-features --features server --all-targets -- -D warnings
clippy-web:
runs-on: rust-ci
steps:
- uses: actions/checkout@v4
- name: Cache cargo registry and build artifacts
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
cargo-${{ runner.os }}-
- name: Cache sccache compilation objects
uses: actions/cache@v4
with:
path: /root/.cache/sccache
key: sccache-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
sccache-${{ runner.os }}-
- name: Add wasm target
run: rustup target add wasm32-unknown-unknown
- name: Format check
run: cargo fmt --check
- name: Clippy (native crates)
run: cargo clippy --workspace --exclude feedsignal-web --all-targets -- -D warnings
- name: Clippy (web crate, server)
run: cargo clippy -p feedsignal-web --no-default-features --features server --all-targets -- -D warnings
- name: Clippy (web crate, wasm client)
run: cargo clippy -p feedsignal-web --no-default-features --features web --target wasm32-unknown-unknown -- -D warnings
clippy-desktop:
# dioxus-desktop (tao/wry) needs GTK/WebKit headers to compile against
# on Linux; rust-ci-desktop is rust-ci plus those, kept as a separate
# image so the other jobs above don't pull a much bigger one for
# headers only this job needs.
runs-on: rust-ci-desktop
steps:
- uses: actions/checkout@v4
- name: Cache cargo registry and build artifacts
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
cargo-${{ runner.os }}-
- name: Cache sccache compilation objects
uses: actions/cache@v4
with:
path: /root/.cache/sccache
key: sccache-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
sccache-${{ runner.os }}-
- name: Clippy (web crate, desktop)
run: cargo clippy -p feedsignal-web --no-default-features --features desktop --all-targets -- -D warnings
test:
needs: check
needs: [fmt, clippy-native, clippy-server, clippy-web, clippy-desktop]
runs-on: rust-ci
steps:
- uses: actions/checkout@v4
@ -60,16 +140,29 @@ jobs:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
cargo-${{ runner.os }}-
# Own key, not shared with the clippy jobs above: this job is the
# only one that needs full codegen (clippy's check-only builds
# produce metadata, not the linkable artifacts `cargo test` needs,
# so there's nothing to share either way), and it never touches
# feedsignal-web, so its target/ stays far smaller than a build
# covering every crate's feature set.
- name: Cache test target directory
uses: actions/cache@v4
with:
path: target
key: target-test-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
target-test-${{ runner.os }}-
- name: Cache sccache compilation objects
uses: actions/cache@v4
with:
path: /root/.cache/sccache
key: sccache-${{ runner.os }}-${{ github.run_id }}
key: sccache-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
sccache-${{ runner.os }}-

2810
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -17,7 +17,10 @@ crates/
chat completion for the relevance judgment on the shortlist.
web/ axum + Dioxus fullstack UI. `server` feature = native binary
(DB, feed polling, LLM calls, scheduler); `web` feature = the
WASM client shipped to the browser. No separate JS/npm stack.
WASM client shipped to the browser; `desktop` feature = a
self-contained native webview app that embeds the `server`
feature's DB/polling/LLM stack in the same process (no HTTP
hop, no separate server to run). No separate JS/npm stack.
```
### Two-stage relevance filtering
@ -98,6 +101,22 @@ cd crates/web
dx serve
```
### Desktop
`feedsignal-web`'s `desktop` feature builds a single native webview binary — DB, feed
polling, LLM calls, and the scheduler all run in-process, so there's no separate server to
start:
```sh
cd crates/web
dx serve --platform desktop
# or: cargo run -p feedsignal-web --no-default-features --features desktop
```
On Linux this needs GTK/WebKit dev headers (`libgtk-3-dev`, `libwebkit2gtk-4.1-dev`,
`libsoup-3.0-dev`, `libayatana-appindicator3-dev`, `librsvg2-dev`, `libxdo-dev` on
Debian/Ubuntu) — the same prerequisites as Tauri, since both build on `tao`/`wry`.
The native crates (`core`, `db`, `feeds`, `llm`) build with plain `cargo check`/`cargo test` and
don't need `dx` at all.

View file

@ -43,3 +43,10 @@ server = [
"dep:serde_json",
"dep:tokio-cron-scheduler",
]
# Self-contained native desktop build: a single binary with the webview UI
# and the "server" feature's DB/feed-polling/LLM stack running in the same
# process. `#[server]` functions dispatch on the "server" feature alone
# (see dioxus-fullstack-macro), so enabling it here makes them execute
# in-process instead of generating an HTTP client stub — no separate axum
# server or network hop needed.
desktop = ["dioxus/desktop", "server"]

12
crates/web/src/desktop.rs Normal file
View file

@ -0,0 +1,12 @@
use dioxus::desktop::{Config, WindowBuilder};
/// Desktop window configuration: a borderless window with no OS title bar.
///
/// Dropping decorations also drops dioxus-desktop's default native menu bar
/// (Window/Edit/Help) — the crate only keeps that menu around to give a
/// decorated window something to hang it off of, and clears it automatically
/// once `with_decorations(false)` is set, so there's nothing further to do
/// here to get rid of it.
pub fn config() -> Config {
Config::new().with_window(WindowBuilder::new().with_decorations(false))
}

View file

@ -1,6 +1,9 @@
mod api;
mod app;
#[cfg(feature = "desktop")]
mod desktop;
#[cfg(feature = "server")]
mod server;
@ -8,5 +11,16 @@ fn main() {
#[cfg(feature = "server")]
server::init_tracing();
// `dioxus::launch`'s automatic platform detection prioritizes "server"
// over "desktop" when both features are enabled (as they are for the
// desktop build, which embeds the server feature for in-process
// `#[server]` calls) — it would stand up an axum server instead of a
// window. Pick the desktop renderer explicitly in that case.
#[cfg(feature = "desktop")]
dioxus::LaunchBuilder::desktop()
.with_cfg(desktop::config())
.launch(app::App);
#[cfg(not(feature = "desktop"))]
dioxus::launch(app::App);
}