From b7333ef970bb023ea599729e81368834197f0b88 Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Mon, 28 Sep 2026 01:22:06 +0000 Subject: [PATCH] HOWTO: build the Rudder desktop app on macOS or Windows, and what is not proven yet --- documentation/HOWTO-build-the-desktop-app.md | 164 +++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 documentation/HOWTO-build-the-desktop-app.md diff --git a/documentation/HOWTO-build-the-desktop-app.md b/documentation/HOWTO-build-the-desktop-app.md new file mode 100644 index 000000000..c0226073a --- /dev/null +++ b/documentation/HOWTO-build-the-desktop-app.md @@ -0,0 +1,164 @@ +# HOWTO: build the Rudder desktop app (macOS / Windows) + +Rudder (`plugin-designs/rudder-voice`) is voice-to-text that runs on your own computer: a web UI plus a +local engine (`backend/rudder-engine`, Go) that drives NVIDIA's NeMo-Speech.cpp speech server. The +desktop app is a Tauri window around the built web tree, with the engine started as a **sidecar**. + +This page is written for an agent following it cold. **Read "What is proven" first**: most of the +desktop path has never been run by anyone, and the steps say so where that is the case. + +## What is proven, and what is not + +| Piece | State | +|---|---| +| The web tree (`bun run build` → `public/dx-maps/`) | Proven. Every landing builds it. | +| The generic Tauri shell (`deployment/src-tauri/`) | Exists. It opens the Research Gallery, builds only a Linux `.deb`, and has never been packaged on macOS or Windows. | +| The engine on Linux (`bun run dev`, laptop mic) | Proven. | +| The engine taking the **webview's** microphone (`RUDDER_AUDIO=browser`) | Proven on Linux: audio streamed over `/api/rudder/stream` came back as the full test sentence. | +| The engine **compiling** for macOS (arm64, x86_64) and Windows (x86_64) | Proven by cross-compiling. It has **never been run** on either. | +| The engine accepting a WebSocket from the Tauri webview (`tauri://localhost`, `http://tauri.localhost`) | Proven against a running engine; another site's origin is refused. | +| NeMo-Speech.cpp runtime for macOS / Windows | Published by NVIDIA (release v0.1.0: `macos-aarch64-cpu`, `macos-aarch64-metal`, `macos-x86_64-cpu`, `windows-x86_64-cpu` / `-cuda` / `-vulkan`). **Never run here.** | +| Rudder as a sidecar, first-run model download, installer | **Not built.** Steps 5–8 below are the work to do. | + +## 1. Prerequisites + +Both platforms: + +- **Bun** (the repo's package manager and script runner): https://bun.sh +- **Go 1.22 or newer** (the engine). +- **Rust** (stable) via https://rustup.rs, then the Tauri CLI, which the repo already pins as a dev + dependency (`@tauri-apps/cli` 2.x, run through `bun run tauri:build`). +- About 2 GB of free disk space (the speech model is 742 MB) and 2 GB of free RAM while it runs. + +macOS: + +- Xcode Command Line Tools: `xcode-select --install`. + +Windows: + +- Microsoft C++ Build Tools ("Desktop development with C++"). +- WebView2 runtime (preinstalled on Windows 11). + +Tauri's own prerequisite list is the reference if anything above is out of date: +https://v2.tauri.app/start/prerequisites/ + +## 2. Get the repo and its packages + +```bash +git clone dx-maps +cd dx-maps +bun install +``` + +## 3. Build the web tree + +```bash +bun run build +``` + +This writes `public/dx-maps/`, the tree every deployment serves, including +`public/dx-maps/rudder-voice/`. The Tauri config points at that folder (`frontendDist`); it does **not** +build it for you (`beforeBuildCommand` is deliberately empty; see `deployment/src-tauri/README.md`). +**Build first, then package.** + +## 4. Build the engine for this machine + +Tauri expects a sidecar binary named with the target triple: + +```bash +# macOS, Apple Silicon +cd backend/rudder-engine && go build -o ../../deployment/src-tauri/binaries/rudder-engine-aarch64-apple-darwin . && cd ../.. +# macOS, Intel +cd backend/rudder-engine && go build -o ../../deployment/src-tauri/binaries/rudder-engine-x86_64-apple-darwin . && cd ../.. +# Windows (PowerShell) +cd backend/rudder-engine; go build -o ..\..\deployment\src-tauri\binaries\rudder-engine-x86_64-pc-windows-msvc.exe .; cd ..\.. +``` + +`rustc -Vv` prints the exact triple (`host:` line) if unsure. + +## 5. Make the Tauri shell start the engine (NOT BUILT: this is the work) + +1. **Declare the sidecar** in `deployment/src-tauri/tauri.conf.json`: + `"bundle": { "externalBin": ["binaries/rudder-engine"], "targets": ["dmg"] }` on macOS, or + `["nsis"]` (or `["msi"]`) on Windows. Keep the file schema-pure: **no `//` comment keys**, Tauri + rejects unknown fields. Run `bun run check:tauri` after editing it. +2. **Add the shell plugin** (`tauri-plugin-shell`) to `deployment/src-tauri/Cargo.toml` and register it + in `src/main.rs`, then spawn the sidecar at startup with these environment variables: + - `RUDDER_AUDIO=browser`: the engine takes the **webview's** microphone. Its own recorders + (`parec` / `pw-record` / `arecord`) exist only on Linux, so on macOS and Windows this is required. + - `RUDDER_HOME=` (Tauri's `app_data_dir()`): where the runtime and model live. + - `RUDDER_PORT=5193` (the default) or a free port you pass on to the page (step 6). + Kill the sidecar when the window closes. On macOS and Windows the engine cannot ask the OS to kill + its own speech server if the engine crashes (Linux-only feature), so the shell should own that. +3. `src/main.rs` currently has no logic by design ("a window around the web tree"). Rudder is the first + desktop plugin with a backend, so this is the first logic it gains; keep it to starting and + stopping the sidecar. + +## 6. Open Rudder, and tell it where the engine is (NOT BUILT) + +The window opens `doxa-maps-spa/doxa-maps-spa/index.html` today. For a Rudder app, open Rudder's own +page, `rudder-voice/rudder-voice/index.html`, and give the element the engine's **absolute** address: + +``` +profile-config = {"apiBase": "http://127.0.0.1:5193/api/rudder"} +``` + +A relative `/api/rudder` would resolve against `tauri://localhost` and reach nothing. How the page +receives that config (a small wrapper page, a query parameter, or the shell injecting it) is not +decided and not built; pick the smallest option and say which in your report. The engine already +answers CORS for the Tauri origins and accepts their WebSocket. + +## 7. The speech runtime and model on first run (NOT BUILT) + +`backend/rudder-engine/setup-runtime.sh` is the reference: it pins the runtime (NeMo-Speech.cpp +**0.1.0**) and the model (`nemotron-3.5-asr-streaming-0.6b.q8_0.gguf`, Hugging Face revision +`ea30d66debe3`, ~742 MB) by SHA-256, and refuses a mismatch. It is **bash and Linux x86_64 only**, so on +macOS and Windows the app must do the same on first run: + +- Download the runtime for this platform from + `https://github.com/NVIDIA/NeMo-Speech.cpp/releases/tag/v0.1.0`. Each asset has a published + `.sha256` next to it; check it. On Apple Silicon, the **`macos-aarch64-metal`** build uses the GPU + and should transcribe far faster than the CPU builds (unmeasured). On a server, the CPU build ran + slower than real time. +- Download the model from the URL and SHA-256 in `setup-runtime.sh`. Verify before use. +- Put them under `RUDDER_HOME`: `bin/nemo-speech-0.1.0---cpu/bin/nemo-speech[.exe]` and + `models/`. The engine derives that runtime path for its own platform + (`` = `macos` / `windows`, `` = `aarch64` / `x86_64`). **Unverified:** the layout inside the + macOS and Windows archives. If it differs, or you use the Metal build, set `RUDDER_NEMO_BIN` to the + real path of the `nemo-speech` binary. +- Show progress: the first start downloads ~750 MB and warms the model (~20 s on a laptop CPU). + +## 8. Package + +```bash +bun run tauri:build +``` + +Run it from the repo root. If the CLI cannot find the config, run `cargo tauri build` inside +`deployment/src-tauri/` (untested either way on macOS/Windows). + +The installer lands under `deployment/src-tauri/target/release/bundle/`: `dmg/` on macOS, `nsis/` or +`msi/` on Windows. + +- **macOS:** the webview needs `NSMicrophoneUsageDescription` in the app's `Info.plist` (Tauri: an + `Info.plist` file next to `tauri.conf.json`), or the microphone request is refused silently. An + unsigned app opens with right-click → Open the first time. Signing and notarising are only needed + to give it to others. +- **Windows:** WebView2 asks for microphone permission on first use. + +## 9. Check it works + +1. Launch the app. Rudder's status reads "Loading speech model…" and then **Ready**. +2. Press **Record**, speak, and watch the words stream into the box. Press **Stop**: the last words + still arrive. +3. `http://127.0.0.1:5193/api/rudder/status` in a browser on the same machine shows + `"engine":"ready"` and `"audio":"browser"`. + +## Known gaps (untested; report what you find) + +- Nothing in steps 5–8 has been run on macOS or Windows. +- The macOS/Windows runtime archive layout, and whether the engine's derived path matches it. +- The first-run download, including resuming an interrupted 742 MB download. +- Mapbox basemaps in the Research Gallery need the token host to allow the Tauri origin + (`deployment/src-tauri/README.md`). Rudder itself needs no token. +- If the engine crashes, its speech server may keep running (and holding ~1 GB) until the app quits.