Guide to setting up the environment and working on RuVox.
- Linux (X11 or Wayland). macOS/Windows are not supported.
- Nix (recommended) — provides a fully reproducible environment: Rust toolchain, Node, pnpm, Python 3.12, uv, libmpv, webkit2gtk, and Tauri's system libraries.
- Without Nix — you'll have to manually install Rust stable + Node 20 + Python 3.12 + system deps (see
buildInputsinnix/devshell.nix:webkitgtk_4_1,libsoup_3,gtk3,libmpv,pipewire/pulseaudio,libappindicator-gtk3,librsvg,pkg-config).
# Interactive shell
nix develop
pnpm install
pnpm tauri dev
# Or run a single command without entering the shell
nix develop -c pnpm install
nix develop -c pnpm tauri devImportant: all commands (
cargo,pnpm,uv,tauri,ruff,pytest) are only available insidenix develop. Don't run commands from an "already open"nix developsession after editingnix/devshell.nix—shellHook(includingXDG_DATA_DIRS,GIO_EXTRA_MODULES,WEBKIT_DISABLE_DMABUF_RENDERER) is only executed when entering the shell. Eachnix develop -c "..."forks a fresh subshell and gets the up-to-date env.
/
├── src/ # React + TypeScript frontend (Vite + Mantine 8)
│ ├── components/ # AppShell, QueueList, Player, TextViewer, icons
│ ├── dialogs/ # PreviewDialog (FF 1.1), Settings
│ ├── lib/ # tauri.ts (typed wrappers), markdown, html, mermaid, wordHighlight, errors
│ └── stores/ # Zustand store selectedEntry
├── src-tauri/ # Rust backend
│ ├── src/
│ │ ├── pipeline/ # Normalization: tracked_text, normalizers/, html_extractor, constants
│ │ ├── storage/ # JSON history + audio files (schema in storage/schema.rs)
│ │ ├── tts/ # TTS engines: piper, silero_native, ttsd subprocess manager
│ │ ├── player/ # tauri-plugin-mpv wrapper (ensure_mpv_alive, seek-suppress)
│ │ ├── commands/ # Tauri commands (#[tauri::command])
│ │ ├── tray/ # System tray (close-to-tray, "Выход")
│ │ ├── state.rs # AppState
│ │ └── lib.rs # Tauri::Builder entry point
│ └── tests/
│ ├── fixtures/pipeline/ # Golden fixtures (37 cases × 3 files)
│ └── golden.rs # Golden integration test
├── ttsd/ # Python subprocess (Silero TTS sidecar)
│ ├── pyproject.toml
│ └── ttsd/
│ ├── silero.py # SileroEngine: load, synthesize
│ ├── timestamps.py # Word timestamp estimation
│ ├── protocol.py # Request/response types
│ └── main.py # Main stdin→stdout JSON loop
├── silero-native/ # Native Silero v5 engine (ONNX Runtime, no Python)
├── docs/ # Documentation (this directory)
├── scripts/ # Utilities (launch-prod, rebuild_prod)
├── nix/
│ └── devshell.nix # Nix dev environment (Rust + Node + Python + Tauri deps)
└── flake.nix # Flake (for nix build .#ruvox and nix develop)
nix develop -c pnpm tauri dev # dev mode with hot reload
nix build .#ruvox && ./result/bin/ruvox # production binarynix develop -c cargo test --manifest-path src-tauri/Cargo.toml # all Rust tests
nix develop -c cargo test --manifest-path src-tauri/Cargo.toml --test golden # golden tests only
nix develop -c pnpm typecheck # TypeScript strict
nix develop -c bash -c "cd ttsd && uv run python -m pytest" # Python subprocessnix build .#ruvox
./result/bin/ruvox.#ruvox builds the slim Tauri release binary (Piper + native Silero), wraps it via wrapProgram (runtime LD_LIBRARY_PATH + GIO_EXTRA_MODULES), and puts mpv in PATH. The .#ruvox-with-silero variant additionally links ttsd (the Python Silero subprocess) into PATH.
First
nix buildrun: thefrontendderivation usespnpm.fetchDepswithlib.fakeHash— Nix will fail with a hash mismatch and print the real hash; substitute it intoflake.nixand rerun the build. This is the standard pnpm2nix procedure.
- Identifiers and comments are in English. User-facing strings (UI, notifications) are in Russian.
- No emoji in code or commit messages.
- Commit format:
<type>(<module>): <short desc>,type ∈ {feat, fix, chore, refactor, docs, test, build}. - Forbidden: "Co-Authored-By: Claude …" or any mention of Claude in a commit.
- Comments only when WHY is non-obvious (a hidden invariant, a workaround for a known bug). Don't comment WHAT.
- Edition 2024 (MSRV 1.85).
tracingfor logs,thiserrorfor domain errors,anyhow::Resultonly at boundaries.unwrapis forbidden in production paths — use?+ typed errors.cargo fmtandcargo clippymust be clean.
strict: truein tsconfig. Noanyunless absolutely necessary.- Functional components only. Don't use
React.FC. - Hooks-first. No class components.
- Prettier for formatting.
- Styling via CSS Modules and the
classNamesprop. - Forbidden:
sx,createStyles,emotion, any Mantine 6/7 legacy. - Forms:
@mantine/form(not react-hook-form, not Formik). - Notifications:
@mantine/notifications. - Hooks:
@mantine/hooks. - Modals:
@mantine/modals(modals.openConfirmModal, etc.).
- No Redux. Global state — Zustand or React context. By default — props +
useState. - React Query is not needed — Tauri invoke fits well with
useEffect+useState.
- No router. Dialogs go through
@mantine/modalsor non-modal floating windows (react-rnd, see PreviewDialog).
- Python 3.12,
uv-managed. - Logs to stderr, JSON requests on stdin, JSON responses on stdout.
ruff checkandpytestmust be green.
- Tauri (Rust) — via
tracingto the process's stderr. In dev mode they're visible in the terminal wherepnpm tauri devis running. - ttsd — Python logs go to stderr → Rust proxies them to
tracing::info!("ttsd: ..."). - Frontend — webview DevTools (right click → Inspect Element in the application window).
In debug builds Tauri's webview enables DevTools. For prod builds you have to either explicitly allow them in tauri.conf.json or build with the devtools feature.
Storage lives in two per-user roots on Linux (on Windows both coincide with
%LOCALAPPDATA%\com.ruvox.app\ — the bundle identifier dir, so the NSIS
uninstaller's "Delete the application data" checkbox can remove it):
~/.local/share/ruvox/history.json— list ofTextEntry. You can open it manually.~/.local/share/ruvox/audio/{uuid}.opus— Ogg-Opus audio (32 kbps VOIP, mono). Plays in any modern player (mpv, VLC, browsers, ...).~/.local/share/ruvox/audio/{uuid}.timestamps.json— word timings.~/.config/ruvox/config.json—UIConfig.
Installs from before 2026-08 keep their files in ~/.cache/ruvox/; the first
launch of a current build migrates them automatically (issue #222).
See Storage schema for details.
git checkout -b feat/short-description # separate branch
# ... edits ...
nix develop -c cargo test ... # run tests
git commit -m "feat(<module>): <desc>" # commit
git push -u origin feat/short-description # push (after approval)New features and fixes follow the standard feature-branch flow.
lefthook install runs from the devshell shellHook. Fast checks (fmt, ruff) gate the commit; heavy checks (clippy, typecheck, eslint, knip) gate the push.
Known upstream quirk: lefthook v2 computes pre-push files via git diff HEAD @{push}, and on the first push of a branch (no upstream yet) falls back to diffing against the local branch named by origin/HEAD — i.e. local main. If local main is absent (deleted, or checked out in another worktree/clone), every pre-push hook exits 128 and the push is rejected. The devshell shellHook keeps a local main ref around as a workaround — if you ever run the hooks outside nix develop and hit fatal: ambiguous argument 'main', create it manually: git branch --no-track main origin/main. Do not "fix" this with --no-verify.