doxa-maps/documentation/HOWTO-build-the-desktop-app.md
Doxa Maps 70ea389db3 Docs: "reusable by default" in the library guides, and Deep Research by its name
- library/components/WARNING.md, library/dashboards/README.md and
  documentation/contributing/README.md taught "graduate into the library at the second
  consumer". They now teach the plugin-designer skill's rule (section 3): a component that
  could serve more than one map is built once in library/, driven by props, and landed through
  the build owner; one-off or experimental pieces stay in a map's folder; a new pattern is a
  pilot, then approved, then rolled out. The contributing guide's "full reasoning" link pointed
  to an index without it; it now points to the skill.
- The app shell doxa-maps-spa is labelled Deep Research. Ten docs still called it the Research
  Gallery (15 mentions): DEPLOY.md, the desktop how-to, embeddability, develop-a-plugin,
  reference-universal-props, reference.md, the shell's EMBED.md, and the plugin-designer skill
  with two of its references. Identifiers (doxa-maps-spa, branch and route names) are unchanged;
  README.md is not touched.
2026-10-01 08:27:04 +00:00

7.1 KiB

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 Deep Research, 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.
bun run desktop:build rudder-voice on Linux Proven: produced Rudder_0.1.0_amd64.deb (8.8 MB) holding the app and the rudder-engine sidecar side by side. The app has not been launched from it yet (no display on the build box).
The shell starting and stopping the engine sidecar Built (deployment/src-tauri/src/main.rs), compiled into that installer; not yet run.
First-run model download Not built. Run setup-runtime.sh into the app's data folder by hand (step 7).

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

git clone <your team's repo URL> dx-maps
cd dx-maps
bun install

3. No web build needed

desktop:build builds the one plugin it packages; bun run build (the web tree) is not a prerequisite.

4. Build the app: one command

bun run desktop:build rudder-voice

It packages ONE plugin whose <profile>.meta.json "targets" include desktop (see skills/plugin-designer/references/targets.md), and nothing in public/ or the committed tree changes:

  1. the plugin is built into .desktop/rudder-voice/, with a one-element page that gives it the engine's address (apiBase: http://127.0.0.1:5193/api/rudder);
  2. its "backend" (backend/rudder-engine) is built for this machine as deployment/src-tauri/binaries/rudder-engine-<target triple> (Tauri's sidecar naming);
  3. tauri build runs with a --config override (the window opens that page, the app is named from the meta label, the engine is bundled); tauri.conf.json is not edited.

The first run compiles Tauri's dependencies (several minutes, about 2 GB under deployment/src-tauri/target/); later runs reuse it.

The shell (deployment/src-tauri/src/main.rs) starts the bundled rudder-engine with RUDDER_AUDIO=browser (the webview captures the mic; the engine's own recorders are Linux-only) and RUDDER_HOME=<the app's data folder>/rudder, and stops it when the app quits. The engine accepts the webview's origins (tauri://localhost, http://tauri.localhost).

5. The speech runtime and model (first run, still by hand)

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-<os>-<arch>-cpu/bin/nemo-speech[.exe] and models/<model file>. The engine derives that runtime path for its own platform (<os> = macos / windows, <arch> = 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).

6. Where the installer lands

desktop:build prints the path. 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.

7. 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)

  • desktop:build has run on Linux only; the app it produced has not been launched yet. Nothing has 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 Deep Research 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.