Compare commits

..

7 commits

Author SHA1 Message Date
Austin Schaefer
04f628b70e Give the test job its own scoped target/ cache
All checks were successful
CI / fmt (pull_request) Successful in 13s
CI / clippy-native (pull_request) Successful in 3m31s
CI / clippy-server (pull_request) Successful in 3m46s
CI / clippy-web (pull_request) Successful in 3m23s
CI / clippy-desktop (pull_request) Successful in 4m27s
CI / test (pull_request) Successful in 7m48s
CI / audit (pull_request) Successful in 25s
test's own compiled objects are worth reusing across runs — it's the
only job needing full codegen, so it can't share anything with the
clippy jobs' check-only builds regardless of key. Scoping it to a key
the clippy jobs don't touch avoids the contention/collision problem
that came from all 5 jobs previously sharing one target/ cache key,
and since it excludes feedsignal-web, its target/ stays much smaller
than the combined cache removed earlier.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 16:06:40 +02:00
Austin Schaefer
9429ca100a Merge branch 'worktree-desktop-support' of ssh://code.austinschaefer.com:22222/schaefera/feedsignal into worktree-desktop-support
All checks were successful
CI / fmt (pull_request) Successful in 11s
CI / clippy-native (pull_request) Successful in 6m1s
CI / clippy-web (pull_request) Successful in 3m50s
CI / clippy-server (pull_request) Successful in 4m59s
CI / clippy-desktop (pull_request) Successful in 4m28s
CI / test (pull_request) Successful in 6m9s
CI / audit (pull_request) Successful in 28s
2026-09-16 15:22:45 +02:00
Austin Schaefer
9a9cd9d1d3 Stop caching target/ and fix sccache's per-run cache key
target/ was the majority of the ~1.5GB cache each of the 5 CI jobs
tarred and uploaded on every run — expensive on a single runner where
those uploads contend for the same disk/network, and pointless since
these jobs build different feature-set combinations into one
identically-keyed target dir anyway. sccache already gives compiler-
level reuse without that duplication.

sccache's cache key was keyed to github.run_id, which never gets an
exact hit, so its ~377MB also re-uploaded in full every single run
with no benefit. Key it to Cargo.lock like the registry cache instead,
so it only re-saves when dependencies actually change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 15:22:30 +02:00
d002d44a2b Merge branch 'main' into worktree-desktop-support
All checks were successful
CI / fmt (pull_request) Successful in 15s
CI / clippy-native (pull_request) Successful in 11m46s
CI / clippy-server (pull_request) Successful in 11m46s
CI / clippy-web (pull_request) Successful in 3m32s
CI / clippy-desktop (pull_request) Successful in 3m37s
CI / test (pull_request) Successful in 10m14s
CI / audit (pull_request) Successful in 21s
2026-09-16 13:00:22 +00:00
Austin Schaefer
ef37690473 Hide the desktop window's title bar and default menu
Some checks failed
CI / fmt (pull_request) Successful in 15s
CI / clippy-web (pull_request) Has been cancelled
CI / clippy-desktop (pull_request) Has been cancelled
CI / test (pull_request) Has been cancelled
CI / audit (pull_request) Has been cancelled
CI / clippy-server (pull_request) Has been cancelled
CI / clippy-native (pull_request) Has been cancelled
Extract desktop window setup into its own crates/web/src/desktop.rs
module (SRP) with a single config() function, wired into main.rs via
LaunchBuilder::desktop().with_cfg(...). Disabling window decorations
also clears dioxus-desktop's default native menu bar as a side effect,
since it exists to give a decorated window something to hang off of.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 14:59:03 +02:00
Austin Schaefer
6c1020c3ef Split CI clippy checks into parallel jobs; move desktop build deps to a dedicated runner image
Some checks failed
CI / fmt (pull_request) Successful in 9s
CI / clippy-native (pull_request) Successful in 2m47s
CI / clippy-server (pull_request) Successful in 2m41s
CI / clippy-web (pull_request) Successful in 1m38s
CI / clippy-desktop (pull_request) Has been cancelled
CI / test (pull_request) Has been cancelled
CI / audit (pull_request) Has been cancelled
The single "check" job ran fmt + four clippy variants (native, server,
web, desktop) as sequential steps, so the desktop feature's clippy check
always waited on the other three even though none of them share
dependencies. Splitting each into its own job lets the runner (capacity
2) run them concurrently instead.

Also drops the per-run `apt-get install` for the GTK/WebKit headers
dioxus-desktop needs — those are now baked into a dedicated
`rust-ci-desktop` runner image (schaefera/infrastructure#TBD) so only the
desktop job's image is bigger, not every job's.

Depends on the `rust-ci-desktop` label being registered on the ci-runner
box before this workflow's clippy-desktop job can pick up work.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 13:30:36 +02:00
Austin Schaefer
67d399ad05 Add desktop platform support via a new feedsignal-web desktop feature
All checks were successful
CI / check (pull_request) Successful in 7m49s
CI / test (pull_request) Successful in 3m37s
CI / audit (pull_request) Successful in 14s
Combines dioxus's desktop (webview) renderer with the existing `server`
feature so `#[server]` functions execute in-process instead of going over
HTTP — one self-contained native binary with DB, feed polling, LLM calls,
and the scheduler, no separate server to run. main.rs picks the desktop
launcher explicitly since dioxus::launch's automatic platform detection
would otherwise prefer the (also-enabled) server platform over desktop.

Verified: cargo check/clippy pass for the new feature (native crates and
existing server/web features unaffected), and running the built binary
opens a window and completes an in-process server-fn call (DB connects,
migrations run) with no HTTP round trip. CI gets a matching desktop
clippy job with the GTK/WebKit apt packages dioxus-desktop needs to
compile on Linux.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 12:36:22 +02:00
6 changed files with 2934 additions and 59 deletions

View file

@ -6,7 +6,14 @@ on:
pull_request: pull_request:
jobs: 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 runs-on: rust-ci
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -17,7 +24,6 @@ jobs:
path: | path: |
~/.cargo/registry ~/.cargo/registry
~/.cargo/git ~/.cargo/git
target
key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }} key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: | restore-keys: |
cargo-${{ runner.os }}- cargo-${{ runner.os }}-
@ -26,30 +32,104 @@ jobs:
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: /root/.cache/sccache path: /root/.cache/sccache
# Not keyed to Cargo.lock: sccache caches individual compiler # Keyed to Cargo.lock, not github.run_id: a per-run key never
# invocations by content hash, so it should accumulate across # gets an exact hit, so actions/cache re-uploads the full
# dependency bumps rather than reset like the target/ cache above. # sccache dir on every single run even when nothing changed.
key: sccache-${{ runner.os }}-${{ github.run_id }} # 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: | restore-keys: |
sccache-${{ runner.os }}- sccache-${{ runner.os }}-
- name: Add wasm target - name: Add wasm target
run: rustup target add wasm32-unknown-unknown 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) - name: Clippy (web crate, wasm client)
run: cargo clippy -p feedsignal-web --no-default-features --features web --target wasm32-unknown-unknown -- -D warnings 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: test:
needs: check needs: [fmt, clippy-native, clippy-server, clippy-web, clippy-desktop]
runs-on: rust-ci runs-on: rust-ci
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -60,16 +140,29 @@ jobs:
path: | path: |
~/.cargo/registry ~/.cargo/registry
~/.cargo/git ~/.cargo/git
target
key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }} key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: | restore-keys: |
cargo-${{ runner.os }}- 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 - name: Cache sccache compilation objects
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: /root/.cache/sccache path: /root/.cache/sccache
key: sccache-${{ runner.os }}-${{ github.run_id }} key: sccache-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: | restore-keys: |
sccache-${{ runner.os }}- 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. chat completion for the relevance judgment on the shortlist.
web/ axum + Dioxus fullstack UI. `server` feature = native binary web/ axum + Dioxus fullstack UI. `server` feature = native binary
(DB, feed polling, LLM calls, scheduler); `web` feature = the (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 ### Two-stage relevance filtering
@ -98,6 +101,22 @@ cd crates/web
dx serve 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 The native crates (`core`, `db`, `feeds`, `llm`) build with plain `cargo check`/`cargo test` and
don't need `dx` at all. don't need `dx` at all.

View file

@ -43,3 +43,10 @@ server = [
"dep:serde_json", "dep:serde_json",
"dep:tokio-cron-scheduler", "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 api;
mod app; mod app;
#[cfg(feature = "desktop")]
mod desktop;
#[cfg(feature = "server")] #[cfg(feature = "server")]
mod server; mod server;
@ -8,5 +11,16 @@ fn main() {
#[cfg(feature = "server")] #[cfg(feature = "server")]
server::init_tracing(); 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); dioxus::launch(app::App);
} }