TARDIS is a Rust-first foundation for a future desktop app that listens to audio, transcribes it, and translates it locally. Today the repository ships a working CLI audio pipeline, a file-based local transcription provider backed by faster-whisper, mock translation flows, and a Tauri shell that drives the same backend from a desktop UI.
- CLI modes exist for device inspection, microphone capture, WAV recording, chunking, chunk saving, mock transcription, mock translation, and file-based local transcription.
- Live audio capture is implemented with
cpal 0.18. - A real local transcription provider exists for WAV files sent to a self-hosted
faster-whisperHTTP server. - A second local provider,
mock-local, exists for deterministic offline development. - Translation is still mock-only.
- The Tauri shell supports live microphone transcription via
start_live_transcription/stop_live_transcriptioncommands. BackendAppEvents are converted to serializableUiAppEventpayloads and emitted as Tauri events to the frontend. Provider selection is available in the UI (mock-local— no Docker;local-whisper— requires Docker). Translation is still mock-only. - Session export: during a live Tauri session the backend accumulates transcript + translation events into a
TranscriptSession(src/app/session.rs). After pressing Stop, the user can click Export JSON / Export Text in the UI to write the session to a path (output/sessions/session_current.json/.txtby default). No audio or WAV chunks are persisted — only the recognised text and session metadata. Two formats: pretty JSON (round-trippable throughserde_json) and human-readable plain text. - The app-facing orchestration layer (
src/app/) is in place:AppService+AppState+AppRuntimeConfig+ a typedAppEventstream. This is the future shared boundary between the CLI modes and the Tauri shell. Reaching it today requirescargo run -- app-mock-flow(sync, no microphone, no Docker). - The runtime settings selected in the UI (provider, language pair, chunk duration, volume threshold) persist across sessions in
<OS config dir>/tardis/runtime.jsonvia thesrc/app/settings_storepure helpers (atomic write-then-rename, load-returns-default-on-missing, normalize-and-validate round trip). See Persistence below. cargo testruns ~219+ unit tests over pure helper logic, provider dispatch, thesrc/app/layer, thesettings_storepersistence layer, and pure Tauri command helpers.
- Rust 2024
cpal0.18 for audio device access and microphone capturehoundfor WAV read/writereqwest+serde+serde_jsonfor local HTTP transcription provider calls and runtime-settings persistence- Tauri 2 for the desktop shell (uses
tauri::Manager::path()for the canonical OS config dir)
Prerequisites: Node 24 (pinned in .nvmrc — nvm install), and a stable Rust toolchain ≥ 1.85 (required for the edition = "2024" workspace).
cargo check
cargo test
cargo runcargo run defaults to devices.
On Linux, the live-mic modes (mic, mic-5s, record-5s, chunk-test, save-chunks-test, mock-transcribe, mock-translate, live-local-transcribe) also need libasound2-dev at build time via the cpal ALSA backend. The apt-get block under Tauri UI Shell Setup installs it alongside the Tauri GUI deps.
This block mirrors the ui-tests job in .github/workflows/ci.yml step-by-step. The CI job has four steps after actions/checkout@v4: actions/setup-node@v4 (with node-version: '24' plus cache: 'npm'), actions/cache@v4 of ~/.cache/ms-playwright, install playwright + chromium (runs npm ci && npx playwright install --with-deps chromium), and run playwright tests. Locally the browser cache is implicit (Playwright writes to ~/.cache/ms-playwright by default) and the npm cache is implicit (npm caches in the user-level cache).
Run once per fresh checkout.
-
Node version — switch to the pinned Node. Equivalent to the
setup-nodestep (node-version: '24',cache: 'npm'):nvm use 24
-
JS dependencies — clean install from the lockfile. Equivalent to the
npm cihalf of theinstall playwright + chromiumstep:npm ci
-
Playwright browser + Linux system deps — equivalent to the
npx playwright install --with-deps chromiumhalf of the same step. On Linux the--with-depshalf requiressudo(theapt-getlibs are shared with the Tauri shell's GUI deps listed in Tauri UI Shell Setup):npx playwright install --with-deps chromium
-
Run the e2e suite — equivalent to the
run playwright testsstep:npm run test:e2e
Note: until real specs ship (in the upcoming session-export feature PR), step 4 exits 0 thanks to the
--pass-with-no-testsflag thetest:e2escript carries; drop the flag — or move it into a futureplaywright.config.jsaspassWithNoTests: true— on the first PR that lands specs.
The Tauri shell bundles the same backend you just built into a desktop window with a settings panel, a live transcript / translation feed, and a JSON / Text export panel. Default workflow (works without extra tools):
cargo run --manifest-path src-tauri/Cargo.tomlOn Debian / Ubuntu, install the system deps from Tauri UI Shell Setup before the first run. macOS / Windows / non-Debian Linux do not need that block.
Alternative (Tauri's standard frontendDist workflow, livelier devtools + automatic webview reload on ui/ changes):
cargo install tauri-cli --locked --version "^2"
cd src-tauri && cargo tauri devBoth commands open the TARDIS v1 window. Pick a provider, click Start Listening, speak — see docs/tauri-ui-shell.md for the byte-accurate manual checklist.
| Command | Purpose | Auto-exit |
|---|---|---|
cargo run -- devices |
Print host, input devices, output devices, and defaults. | Yes |
cargo run -- mic |
Capture from the default microphone continuously. | No |
cargo run -- mic-5s |
Capture from the default microphone for 5 seconds. | Yes |
cargo run -- record-5s |
Record 5 seconds to output/mic_test.wav. |
Yes |
cargo run -- chunk-test |
Capture 10 seconds and print 1-second chunk metadata. | Yes |
cargo run -- save-chunks-test |
Capture 10 seconds and save each 1-second chunk under output/chunks/. |
Yes |
cargo run -- mock-transcribe |
Capture, classify chunks, and print mock transcript results. | Yes |
cargo run -- mock-transcribe-file output/chunks/chunk_001.wav |
Run the mock transcriber over a saved WAV chunk. | Yes |
cargo run -- mock-translate |
Capture, mock-transcribe, then mock-translate each chunk. | Yes |
cargo run -- local-transcribe-file [--provider <name>] <path> |
Send a WAV file to a local transcription provider and print plaintext output. | Yes |
cargo run -- live-local-transcribe [--provider <name>] |
Chunk-by-chunk live transcription from the default microphone. Emits typed AppEvents (StatusChanged, Transcript, Translation, Error) to the console via format_app_event_for_console — the same event stream the Tauri shell will consume. Default provider is mock-local (no Docker). Silence chunks are skipped. This is not true streaming. |
Yes |
cargo run -- app-mock-flow |
Sync smoke test of the app orchestration layer: start_listening_mock → run_mock_text_flow("mock transcript: speech detected") → run_mock_text_flow("") (silent-skip demo) → stop_listening. Prints every emitted event and the final state. No microphone, no Docker, no fs. |
Yes |
cargo run -- settings-default |
Print the default AppRuntimeConfig as pretty JSON. Mirrors the file the Tauri shell writes on first save, so it can be piped into a fresh runtime.json. No FS writes, no microphone, no Docker. |
Yes |
cargo run -- settings-validate <path> |
Load + validate a settings JSON file at <path>. Prints the resolved values and exits 0 on success or 1 with the underlying SettingsStoreError on parse / validation / I/O failure. Mirrors the load_runtime_settings Tauri command. |
Yes |
cargo run -- session-export-demo |
Create a fixture TranscriptSession (one transcript + one translation event), then export it as pretty JSON and plain text to output/sessions/session_demo.{json,txt}. No microphone, no Docker, no Tauri shell — reusable as a quick smoke test for the entire session/export/file-store pipeline. |
Yes |
The provider selected by local-transcribe-file implements the shared transcription::LocalTranscriptionProvider trait.
| Provider | Selector | What it does |
|---|---|---|
| Local faster-whisper HTTP server | --provider local-whisper or omit the flag (for local-transcribe-file) |
Sends a WAV file to http://localhost:8000/v1/audio/transcriptions and prints the text field from the OpenAI-style JSON response. |
| Deterministic offline stub | --provider mock-local (default for live-local-transcribe) |
Returns mock transcript for <basename> without using the network. |
Start the Docker-backed provider with:
docker compose -f docker/faster-whisper/docker-compose.yml upThen transcribe a saved chunk:
cargo run -- local-transcribe-file output/chunks/chunk_001.wavOr run chunk-by-chunk live transcription (no Docker needed with the default mock-local provider):
cargo run -- live-local-transcribe
cargo run -- live-local-transcribe --provider local-whisperOperator details for that container live in docker/faster-whisper/README.md.
The CLI commands in Quick Start and CLI Modes compile against cpal and hound with no GUI dependencies. The Tauri shell additionally pulls in the WebKit / GTK / appindicator toolchain, which Debian / Ubuntu systems do not ship by default. Before the first cargo run --manifest-path src-tauri/Cargo.toml, install the system deps that CI installs (the bash block below is verbatim from .github/workflows/ci.yml lines 19–28):
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libasound2-dev \
libwebkit2gtk-4.1-dev \
libgtk-3-dev \
libappindicator3-dev \
librsvg2-dev \
patchelfmacOS, Windows, and non-Debian / Ubuntu Linux distributions do not need this block — the WebKit / GTK surface is only required for the Tauri webview to render on Debian / Ubuntu.
The repo now includes a desktop shell in src-tauri/ plus static frontend assets in ui/.
Run it with:
cargo run --manifest-path src-tauri/Cargo.tomlWhat the shell does today:
- Session settings panel: pick a
transcription_provider,source_language,target_language,chunk_duration_ms, andvolume_thresholdbefore pressing Start Listening. Settings are pre-populated from a persistedruntime.jsonif one exists, otherwise fromAppRuntimeConfig::default()(src/app/config.rs). The provider dropdown is rebuilt from the backend's supported-provider list so a future provider shows up without touching the HTML. The settings lock while a session is in progress. - Live transcription: Click Start Listening to capture audio from the default microphone, chunk it using the requested chunk size, classify each chunk with the requested threshold, and transcribe speech-like chunks through the selected provider. Transcript and translation events flow into the window via Tauri events. Click Stop to end the session. Translation uses the selected source/target language pair (no hardcoded en/es).
- Provider selector: choose
mock-local(no Docker, deterministic) orlocal-whisper(requires Docker). - Default
transcription_providerismock-local;local-whisperrequires the Docker container to be running. - Mock-only controls preserved for dev reference:
start_mock_listening,get_mock_transcript,get_mock_translation. - "Local WAV Transcription" card: validate a path, call
transcribe_wav_file_local, surface the transcript or a user-facing error. - "Session Export" card: after a Stop, write the accumulated session to a JSON or plain-text path via
export_current_session_json/export_current_session_text(powered bysrc/app/session_export.rs+src/app/session_store.rs). A "Show current JSON" button callsget_current_session_jsonfor a peek.
The desktop shell exposes the following #[tauri::command] entry points. All paths and event types are unit-tested against the documented contracts in src-tauri/src/lib.rs; the binding to the Tauri runtime is verified manually (docs/tauri-ui-shell.md):
| Command | Purpose |
|---|---|
start_live_transcription(config: Option<AppRuntimeConfig>) |
Spawns a background worker thread that captures audio from the default mic, transcribes speech-like chunks via the selected provider, and emits an app-event Tauri event for every [AppEvent]. Returns "started with provider <name>". |
stop_live_transcription() |
Signals the active session to stop; the worker thread exits and the [SessionCleanupGuard] resets is_running + stop_signal. Idempotent — a stale Stop on a torn-down session returns NO_SESSION_RUNNING_ERR. |
transcribe_wav_file_local(path: String) |
File-based local transcription through local-whisper (delegates to the same [LocalWhisperClient] the CLI uses; "Docker not running?" errors are normalised into a user-facing string). |
export_current_session_json(path: String) |
Saves the in-memory [TranscriptSession] as pretty JSON at path; consumes the snapshot. |
export_current_session_text(path: String) |
Saves the in-memory [TranscriptSession] as plain text at path; consumes the snapshot. |
get_current_session_json() |
Read-only JSON render of the current session — does NOT consume (a "Show current JSON" peek in the UI). |
get_supported_transcription_providers() |
Backend's ["mock-local", "local-whisper"] list — the UI rebuilds the provider dropdown from this so a future provider shows up without touching the HTML. |
get_default_runtime_config() |
Default [AppRuntimeConfig] for first-load pre-population. |
load_runtime_settings() |
Loads <OS config dir>/tardis/runtime.json, falls back to defaults on first run or if the file is missing. |
save_runtime_settings(config) |
Atomically writes the validated + normalised config (write-then-rename via <file>.tmp). |
Five mock-only controls preserved for dev reference (no microphone, no Docker, no CPAL): get_app_status(), start_mock_listening(), stop_mock_listening(), get_mock_transcript(), get_mock_translation(). |
What it does not do yet:
- System audio capture
- Run a real translation backend
- Streaming/partial transcripts
- Recording / permission UX before any release candidate
More detail lives in docs/tauri-ui-shell.md.
The Tauri shell persists session settings to <OS config dir>/tardis/runtime.json so the same provider / language pair / chunk size / threshold come back next launch:
- Linux:
$XDG_CONFIG_HOME/tardis/runtime.json(typically~/.config/tardis/runtime.json). - macOS:
~/Library/Application Support/tardis/runtime.json. - Windows:
%APPDATA%\tardis\runtime.json.
Pure helpers live in src/app/settings_store.rs — atomic write (write-then-rename via <path>.tmp), load-returns-default-on-missing, normalize-and-validate round-trip. The Tauri commands load_runtime_settings / save_runtime_settings resolve the OS path via tauri::Manager::path() and delegate to the pure helpers; the UI calls load_runtime_settings on init (falling back to get_default_runtime_config if the file is missing or unreadable) and save_runtime_settings on every committed settings change.
CI does not verify the actual filesystem path on a developer machine — only the pure round-trip behaviour in unit tests. The first-run contract is documented in docs/tauri-ui-shell.md.
src/
main.rs CLI dispatcher
lib.rs Library surface shared by the CLI binary and src-tauri
config.rs Centralized constants
audio/ CPAL capture, chunking, recording, pure audio helpers
transcription/ Traits, mocks, file pipeline, local providers, live-local pipeline
translation/ Traits, mocks, live mock translation pipeline
app/ App-facing orchestration layer (CLI + Tauri shared boundary)
events/ AppStatus + AppEvent stream + payload structs
config/ AppRuntimeConfig + validate_runtime_config + normalize_runtime_config + supported providers
settings_store/ Pure load/save to <OS config dir>/tardis/runtime.json (atomic write, normalize-and-validate round trip)
state/ AppState (status + config + last_transcript/translation)
service/ AppService orchestrator (start_listening_mock, stop_listening, run_mock_text_flow)
src-tauri/ Tauri shell crate (mock commands + transcribe_wav_file_local + load_runtime_settings + save_runtime_settings)
ui/ Static frontend assets for the shell
docker/faster-whisper/ Local Docker transcription stack
docs/ Supplemental project notes
The src/app/ layer is the future shared boundary: today it powers
cargo run -- app-mock-flow; once the Tauri shell moves past
mock-only controls, it will host the same AppService API the CLI
already exercises, end-to-end from microphone capture to UI event
sink.
GitHub Actions runs on every PR and push to main.
| Check | Command |
|---|---|
| Formatting | cargo fmt --check |
| Compilation | cargo check |
| Tauri compilation | cargo check --manifest-path src-tauri/Cargo.toml |
| Unit tests | cargo test |
CI does not run hardware/audio/manual commands (live-local-transcribe, mic, record-5s, etc.) — those remain local validation only. CPAL streams, microphones, Docker providers, and Tauri runtime are not exercised in CI.
The desktop shell is per-OS bundle-ready: src-tauri/Cargo.toml + src-tauri/tauri.conf.json configure Tauri 2 such that cargo tauri build produces a native bundle for the host it runs on. Cross-platform coverage needs a 3-runner CI matrix; the project does not ship binaries yet. Anything below prefaced with [x] already ships with tardis-v0.1.0 (the Node-24-pin / Linux-libasound2 / Tauri-shell-setup / UI-tests-CI-gate milestone at bf39ea2); unchecked items are required before the next tag ships.
- Per-OS bundle config is wired.
src-tauri/Cargo.toml+src-tauri/tauri.conf.jsonconfigure Tauri 2;cargo tauri buildproduces native bundles from the repo root. - Linux
apt-getblock + macOS / Windows / non-Debian Linux note live in Tauri UI Shell Setup; the same block CI installs is what a developer needs locally. - Pick the next tag. Once the session-export feature lands and a real translation backend is wired in, the natural next milestone is
tardis-v0.2.0(a bigger bundle thantardis-v0.1.0, which marks the Node-24 / linux-libasound2 / Tauri-shell-setup / UI-tests-CI-gate milestone). - Bump version strings.
Cargo.toml(tardisCLI),src-tauri/Cargo.toml(tardis-ui-shell), andsrc-tauri/tauri.conf.json(bundle.identifier,bundle.version,productName) must all agree for consistent metadata (CLI binary version, Tauri bundle installer version, AppImage //usr/bin/tardisversion). Mismatches do not fail the build but produce inconsistent version strings across artefacts. - Run
cargo build --releasefor the CLI binary (no UI shell — useful for headless / CI smoke users who do not need the desktop app). - Run
cargo tauri buildfor the desktop shell to produce native bundles:.deb+.AppImage+.rpm(Linux — needsdpkg-deb,rpmbuild,fakeroot,appimagetool).dmg(macOS —cargo tauri builddoes the disk-image; code signing needscodesign+notarytoolfor notarisation).msi+.exe(Windows — needssigntoolfor Authenticode signing; WiX for.msialready ships with Tauri 2)- Build matrix: each platform builds on its own host — Tauri 2 has no first-class OS-cross-compile path (the project itself does not adopt
cargo-zigbuildorcargo-xwinworkarounds; they remain an opt-in for downstream forks). CI needs three runners (ubuntu-latest/macos-latest/windows-latest).
- Code signing + notarisation.
- macOS:
codesign --deep --timestamp --options=runtime+xcrun notarytool submit --wait+ staple the ticket. - Windows:
signtool sign /fd SHA256 /tr http://timestamp.digicert.com+ Authenticode timestamp authority. - Linux: GPG
.sigartifacts sidecar to.deb/.rpm(.AppImageis unsigned by convention; document that). - Tauri's
tauri.conf.jsonbundle.macOS.signingIdentity,bundle.windows.certificateThumbprint, andbundle.beforeBundleCommandare where these hooks live.
- macOS:
- GitHub Release workflow (
.github/workflows/release.yml): triggers on the canonicaltardis-vX.Y.ZSemVer tag pattern (thev*.*.*legacy form was retired alongside the deprecatedv0.1.0tag — see RELEASES.md § Tagging convention for the namespace rationale), runs the build matrix above, uploads.deb,.dmg,.msi,.AppImage, plus checksums (SHA256SUMS.txt) to a GitHub Release with auto-generated notes from commits since the prior tag. - Lockfile reproducibility.
Cargo.lockcommits prevent supply-chain drift across builds; bump it on every release-prep withcargo update --workspaceand review the diff in the same commit that bumps version strings. - First-run UX + privacy. Per the Privacy Note: visible recording-state indicator, microphone-permission prompt honouring the OS convention, and a "Stop" button reachable in one click — all required before any external distribution.
- Install / update docs. A
RELEASES.md(or a## Releasessection heading) linking to the GitHub Releases page plus per-platform install walkthrough (apt install ./tardis.deb,brew install --cask tardis,winget install tardis/next-next-finish on Windows) ships next to the binary in the same release commit.
Tracks the bigger goal in ROADMAP §5 "Later" — once a release workflow exists, TARDIS stops being a local-only spike.
This repo deliberately separates pure logic from hardware glue.
- Unit tests cover pure helpers and provider-selection logic.
- CPAL streams, device permissions, microphones, and filesystem-heavy audio flows are verified through
cargo run -- <mode>. - The working conventions are documented in AGENTS.md and DEVELOPMENT_RULES.md.
Current backend flow:
Microphone or WAV file
-> audio helpers / chunking
-> activity classification
-> transcription trait boundary
-> optional translation trait boundary
-> console output today, AppEvent stream tomorrow
The AppEvent stream is the typed event surface emitted by
AppService (the new src/app/ orchestration layer). Today the
CLI's app-mock-flow mode prints it directly; the Tauri shell will
relay each event into the webview once it stops being mock-only.
Design constraints that shape the codebase:
- Pure logic should stay testable next to the module that owns it.
- CPAL and file I/O glue should remain small and manually verifiable.
- Real backends should fit behind existing transcription and translation abstractions.
- The Tauri shell should reuse the backend rather than fork its own audio pipeline.
The project handles microphone audio and may later handle system audio. Any production UI should surface explicit recording state, permission handling, and clear user controls before this becomes a real end-user application.