Status: v1 implemented (2026-08-21)
Run the Tauri app's UI as a browser-hosted web app so diffs can be reviewed on a remote server without a desktop environment.
The existing pieces already do most of the work: the UI runs in plain browsers
(IS_TAURI detection + mock demo mode), LLM activity streaming is already an
axum SSE server (activity_stream.rs, zero tauri imports), and all commands are
plain serde-typed functions.
New pieces:
Stateshim (crates/diffcore-tauri/src/state_shim.rs): aDerefnewtype over&AppState, cfg-swapped fortauri::Statewhen thedesktopfeature is off. Command signatures change fromtauri::State<'_, AppState>to a bareState<'_, AppState>resolved by a conditionaluse. No per-command splitting.- Feature gating (
crates/diffcore-tauri/Cargo.toml):desktop(default) enables tauri + plugins;webenables axum static serving. Both binaries carryrequired-features.build.rsonly callstauri_build::build()when the desktop feature is enabled. The web binary links no webkit/gtk. - Process-global background runtime (
runtime.rs): background jobs andblock_ongo through one shared tokio runtime instead oftauri::async_runtime, so command logic works identically under tauri and axum. diffcore-webbinary (src/bin/web.rs+src/web_server.rs): axum server withPOST /api/invoke/{cmd}: JSON body = command args (camelCase keys, mirroring tauri IPC 1:1), dispatched by a hand-written match; sync commands run underspawn_blocking. Unknown/desktop-only commands → 501.GET /api/health:{ "ok": true, "default_repo": <--repo flag> }; the UI probes this to enter web mode.- the existing SSE routes merged in (same origin;
activity_stream_base_urlis set to""sostream_urlcomes out relative). - static file serving of the built UI (
--ui-dir, defaultcrates/diffcore-tauri/ui/dist).
- Frontend third mode (
ui/src): transport priority is Tauri IPC → web API (same-origin/api/healthprobe at bootstrap) → mock demo. Branches that mean "real backend vs mock" useHAS_BACKEND; branches that are truly desktop-only (updater, file-watch events, editor opening) stay onIS_TAURI.
- No auth. Binds
127.0.0.1by default;--hostto override. Remote access via SSH tunnel or a reverse proxy with auth. Do not expose bare. Requests are still guarded against DNS rebinding and CSRF: Host/Origin headers must name an allowed host (localhost, the bind host, or--allowed-hostvalues, e.g. a reverse-proxy domain), and invoke bodies must be well-formedapplication/json. - Single-user server. One shared
AppState: concurrent clients analyzing different repos/refs overwrite each other'slast_analysis/refinement state. Fine for the personal-tunnel posture; multi-user needs per-session state. - No git-HEAD/manifest watch push on web (tauri events; UI already tolerates
the failed
watch_git_headinvoke). Manual refresh covers it. - Updater and open-in-editor stay desktop-only.
# nix (the package ships all three modes; UI assets are bundled)
nix run .#web -- --repo /srv/checkouts/myrepo --port 4400
nix run .#desktop # tauri app
nix run .#cli -- --help # diffcore CLI
# plain cargo
cargo build --release --bin diffcore-web --no-default-features --features web
npm --prefix crates/diffcore-tauri/ui run build
diffcore-web --repo /srv/checkouts/myrepo --port 4400
ssh -L 4400:localhost:4400 server # then open http://localhost:4400
When binding non-loopback (--host 0.0.0.0) or fronting with a reverse proxy,
pass each hostname/IP clients will use via --allowed-host. The Host/Origin
guard rejects anything else with 403.
Rust integration test spins the router and exercises /api/health plus an
invoke round-trip. Both feature configurations must compile; the full workspace
test suite and the UI build stay green.