fix(voice): ship the voice domain in the desktop build (#4901)#4917
Conversation
…4901) The Tauri shell embeds the core in-process (tinyhumansai#1061), so its dependency declaration is what actually ships. It has carried `default-features = false` since the in-process move, back when the core crate had no `[features] default` list at all — a harmless no-op. tinyhumansai#4833 then made `voice` a default-ON compile-time feature so slim builds could drop it. That silently collided with the pre-existing `default-features = false`: the shipped app has been built WITHOUT the voice domain ever since, so the `#[cfg(feature = "voice")]` controllers in `src/core/all.rs` were never registered and every `openhuman.voice_*` RPC answered "unknown method". Evidence: `hound` (reachable only via `voice = ["dep:hound", ...]`) was absent from the shell's dependency graph while present in the root crate's. Sentry confirms in production — `voice_status` (46,735 events / 56 users) and `voice_server_status` (46,592 / 52) both unknown-method, first seen 2026-07-14, hours after tinyhumansai#4833 landed, matching first bad release 0.58.19. - Cargo.toml: opt `voice` back in explicitly, and document that every future core default must be re-added here. - lib.rs: assert `VOICE_COMPILED_IN` at compile time. Cargo features are per-crate, so the shell cannot `#[cfg]` on the core's features; this const assert is the only way to make a voice-less desktop binary fail the build instead of failing silently at runtime. Verified red/green. - sttClient.ts: the "restart to pick up the latest core sidecar" copy was written for tinyhumansai#1289 (a stale bundled sidecar, removed in tinyhumansai#1061) and cannot ever resolve a compile-time gate. Replaced with an accurate reason and a real action, keeping the "unavailable in this build" substring that MicComposer matches to suppress the pointless retry loop. Tests asserting the misleading restart hint were pinning the bug; they now assert the accurate message and that restart advice is not reintroduced. Fixes tinyhumansai#4901
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThe desktop build now enables and verifies the core voice feature. STT clients convert missing voice RPCs into a structured unavailable-build error, and the microphone UI displays localized copy without retrying. A canvas validation test now waits for asynchronous metadata updates. ChangesVoice build and runtime handling
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant MicComposer
participant STTClient
participant OpenHumanCore
MicComposer->>STTClient: Request transcription
STTClient->>OpenHumanCore: Call voice transcription RPC
OpenHumanCore-->>STTClient: Return unknown method
STTClient-->>MicComposer: Return VoiceNotCompiledError
MicComposer-->>MicComposer: Display localized mic.voiceNotCompiled message
Possibly related PRs
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/src/features/human/voice/sttClient.ts`:
- Around line 16-18: Replace the hardcoded VOICE_NOT_COMPILED_MESSAGE in
sttClient.ts with a stable error code or typed error. At the UI presentation
boundary, detect that code/type and render translated copy through useT(),
adding the corresponding translation key and real value to every supported
locale.
- Around line 87-89: Limit the VOICE_NOT_COMPILED_MESSAGE rewrite in both
transcription paths at callCoreRpc sites
(app/src/features/human/voice/sttClient.ts:87-89 and
app/src/features/human/voice/sttClient.ts:163-165) to errors proven to originate
from the local core transport. Carry transport provenance or a structured error
kind through callCoreRpc, and preserve the original unknown-method message for
remote transports.
In `@src/openhuman/voice/mod.rs`:
- Around line 95-104: Move the VOICE_COMPILED_IN constant implementation from
src/openhuman/voice/mod.rs into a sibling module such as compile_status.rs,
preserving its cfg!(feature = "voice") behavior and documentation. Keep mod.rs
limited to module declarations and re-exports, including re-exporting
VOICE_COMPILED_IN from the new module.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 6658b7be-0414-47ee-beb2-cd0844cbb4db
⛔ Files ignored due to path filters (1)
app/src-tauri/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (7)
app/src-tauri/Cargo.tomlapp/src-tauri/src/lib.rsapp/src/features/human/MicComposer.test.tsxapp/src/features/human/MicComposer.tsxapp/src/features/human/voice/sttClient.test.tsapp/src/features/human/voice/sttClient.tssrc/openhuman/voice/mod.rs
Conflict in app/src-tauri/Cargo.toml: the media feature gate (tinyhumansai#4804/tinyhumansai#4840) landed on main and hit the same trap as tinyhumansai#4901 — `default-features = false` silently dropping a default-ON core gate from the shipped desktop build. It forwarded `media`; this branch forwards `voice`. Both are required, so the resolution keeps both rather than either side. Merged the two comments into one rule ("every default-ON gate must be forwarded explicitly, a missing gate vanishes silently") and noted that `tokenjuice-treesitter` is the remaining un-forwarded default (tinyhumansai#4918). Verified after merge: `cargo tree -e features -i openhuman` resolves both `feature "media"` and `feature "voice"`; shell `cargo check` green (so the VOICE_COMPILED_IN assert passes); 212 related Vitest tests pass; fmt clean.
…t of mod.rs Addresses review feedback on tinyhumansai#4917. Two of three findings were valid. i18n (valid): `MicComposer` interpolated the raw English error into the translated `mic.transcriptionFailed` template ("Transcription failed: {message}"), so non-English locales rendered a half-translated sentence. CI could not catch this — `i18n:english:check` scans locale files, not service code. - sttClient now throws a typed `VoiceNotCompiledError` carrying a stable `VOICE_NOT_COMPILED_CODE`. The English message is kept deliberately: it is what reaches debug logs and Sentry. - `isVoiceNotCompiledError` duck-types on `code` rather than `instanceof` — the error crosses async/retry boundaries where prototypes can be stripped. - `MicComposer` branches on the code and renders `mic.voiceNotCompiled`. - Key added to all 14 locales with real translations (no em dashes, per AGENTS.md). `i18n:check` and `i18n:english:check` both pass. mod.rs (valid): AGENTS.md requires mod.rs be export-only. Moved `VOICE_COMPILED_IN` to a sibling `compile_status.rs`, deliberately ungated (it must exist in both gate states — reporting which state we are in is its purpose) and re-exported. Tests pin the const to `cfg!(feature = "voice")` rather than a literal, so they hold for both builds. transport provenance (skipped): not reachable. `setActiveCoreTransport` has exactly one occurrence in the tree — its own definition — so `_activeTransport` is always null and every `callCoreRpc` hits the local core. The `unknown method` rewrite condition is also pre-existing, unchanged by tinyhumansai#4917. Threading provenance through the shared RPC client for a dead path is not minimal; it belongs with whatever work activates the iOS/tunnel transport. Also fixes a latent test defect: MicComposer.test.tsx hand-listed its sttClient mock exports, so any newly imported export silently resolved to undefined and threw a TypeError at the call site. Now spreads importOriginal(). Verified: compile_status tests pass with voice ON and OFF (2/2 each); shell cargo check green (const assert resolves via the re-export); 78 voice tests pass; i18n checks, tsc, ESLint, Prettier, cargo fmt all clean; locale diff is 27 insertions / 0 deletions.
M3gA-Mind
left a comment
There was a problem hiding this comment.
PR #4917 — fix(voice): ship the voice domain in the desktop build (#4901)
Walkthrough
Since v0.58.19 the shipped desktop app carried default-features = false on its openhuman_core dependency, which silently dropped the default-ON voice compile-time gate added by #4833 — so every openhuman.voice_* controller was unregistered and answered "unknown method" at runtime (46k+ Sentry events). This PR re-forwards voice (and documents the whole forwarding rule), adds a compile-time const assert against the core's always-compiled VOICE_COMPILED_IN facade so a voice-less desktop binary now fails the build instead of shipping broken, and replaces the stale "restart to pick up the latest core sidecar" copy (obsolete since the sidecar was removed in #1061) with a typed VoiceNotCompiledError that drives a properly localized mic.voiceNotCompiled message across all 14 locales.
The diagnosis is precise, the fix is minimal and correct, tests are red/green verified, and the AGENTS.md conventions (mod.rs export-only, translations in every locale, no em dashes in translation values) are all respected. No blocking issues found — approving.
Changes
| File | Summary |
|---|---|
app/src-tauri/Cargo.toml |
Add voice to the openhuman_core feature list; expand the comment into an enforced "forward every default-ON gate" rule. |
app/src-tauri/Cargo.lock |
Re-adds hound + lettre (reachable only via the voice feature) to the shell graph. |
app/src-tauri/src/lib.rs |
const _: () = assert!(openhuman_core::…::VOICE_COMPILED_IN, …) — turns a silent runtime gap into a build failure. |
src/openhuman/voice/compile_status.rs |
New ungated module exposing VOICE_COMPILED_IN = cfg!(feature = "voice"), with tests pinning it to the gate in both build states. |
src/openhuman/voice/mod.rs |
Re-export VOICE_COMPILED_IN from the always-compiled facade. |
app/src/features/human/voice/sttClient.ts |
Introduce VoiceNotCompiledError + VOICE_NOT_COMPILED_CODE + isVoiceNotCompiledError; both STT paths throw the typed error on unknown method. |
app/src/features/human/MicComposer.tsx |
Branch on isVoiceNotCompiledError to render localized mic.voiceNotCompiled instead of splicing raw English into {message}. |
app/src/features/human/{sttClient,MicComposer}.test.* |
Updated + new tests for the not-compiled message, restart-copy removal, retry suppression, and localized rendering. |
app/src/lib/i18n/*.ts (14) |
Add mic.voiceNotCompiled with real translations in every locale. |
Actionable comments (1)
💡 Refactor / suggestion
1. app/src/features/human/voice/sttClient.ts:45 — make isVoiceNotCompiledError a type guard
The predicate returns a bare boolean, so callers that later need to read err.code/err.message after the check still have to cast err from unknown. Narrowing to VoiceNotCompiledError costs nothing (the class already carries code) and documents intent. This is polish, not a defect — the current call sites only branch on it.
Suggested change:
// before
export function isVoiceNotCompiledError(err: unknown): boolean {
// after
export function isVoiceNotCompiledError(err: unknown): err is VoiceNotCompiledError {Nitpicks (1)
app/src/lib/i18n/en.ts:405— a stray blank line now separatesmic.voiceNotCompiledfrom the// Reflectionsblock. Harmless (matches the section spacing) but slightly asymmetric with the other 13 locales, which inserted the key inline. Leave as-is or drop the blank line.
Questions for the author (0)
None.
Verified / looks good
- Const assert is sound:
assert!(const_bool, "literal")is valid in aconstcontext, andVOICE_COMPILED_IN = cfg!(feature = "voice")evaluating tofalsetriggers a genuine E0080 compile failure — matches the PR's red/green table. The message is a bare literal (no format args), which const panic requires. mod.rsstays export-only (AGENTS.md): the constant lives in siblingcompile_status.rs;mod.rsonly re-exports it.compile_status.rsand itspub modline are correctly ungated so they exist in both feature states, andstub.rsdoes not redefine the symbol, so there's no glob/explicit re-export collision when voice is OFF.- Retry suppression is intact:
VoiceNotCompiledError.messageretains theunavailable in this buildsubstring, whichPERMANENT_ERROR_PATTERNSmatches →isTransientErrorreturnsfalse→ the error rethrows throughtranscribeWithRetry/transcribeWithFallbackwithout a WAV re-encode, and reachesfinalizeRecordingwith its prototype intact. - Duck-typing on
code(notinstanceof) is the right call for an error crossing async/retry boundaries, and a pinning test (keeps the "unavailable in this build" substring) guards the coupling. - i18n complete: all 14 locale files (
ar bn de en es fr hi id it ko pl pt ru zh-CN) carrymic.voiceNotCompiled; interpolation-free values, noU+2014em dashes in any translation value. - Test coverage: the "does not retry permanent errors" and cloud/factory
unknown methodtests were updated to assert the new copy and the absence of the restart hint; a new MicComposer test proves the localized string (not raw developer copy) reachesonError.
Retracting the APPROVED state — this review is intended as comment-only. The automated harness defaulted to approving, but the merge/approve decision is the human maintainer's. The technical findings below stand as review comments, not an approval.
W5 review (comment-only — the maintainer decides merge)I ran our review harness and then verified the substantive claims against the code myself. Two process notes up front:
CI: the 2 "failures" are stale, not real
The two early failures were superseded by the 19:02 pass (the checklist re-runs on PR-description edits). Every substantive check is green — Rust Core Coverage, Rust Tauri Coverage, Frontend Checks (quality/i18n/docs/coverage), Rust Feature-Gate Smoke (gates off), Rust Quality (fmt/clippy), PR CI Gate. No real code failure. (The only open review state is CodeRabbit's earlier Fix correctness — sound
Non-blocking nits
Verdict: sound. The diagnosis, the feature-forward fix, and the compile-time guard are all correct and well-tested; CI is effectively green. Deferring the approve/merge decision to the maintainer. |
Return `err is VoiceNotCompiledError` instead of a bare boolean so callers that read `.code`/`.message` after the check get the narrowed type without re-casting from `unknown`. Behavior unchanged — the body still duck-types on `code`. Addresses M3gA-Mind review nit on tinyhumansai#4917. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
W5 re-review (comment-only — head
|
M3gA-Mind
left a comment
There was a problem hiding this comment.
Approving the code (head 9c3a8e14). The diagnosis and fix are correct and well-guarded:
openhuman_corefeatures["media"]→["media","voice"]re-forwards the default-ON gate thatdefault-features=falsehad been dropping (the root cause of theopenhuman.voice_*"unknown method" storm).- The compile-time
const _: () = assert!(openhuman_core::…::VOICE_COMPILED_IN, …)(ungated per-crate const, separate Cargo world) turns a future regression into a build failure (E0080) instead of a silent ship. - Frontend: typed
VoiceNotCompiledError→ localizedmic.voiceNotCompiledacross all 14 locales; retry-suppression preserved;isVoiceNotCompiledErroris now a propererr is VoiceNotCompiledErrortype guard.
Two things still to clear before merge (approval is a code sign-off, not a bypass of these):
- CI Lite is still running at this head — the required PR CI Gate / Frontend / Rust Coverage jobs haven't reported yet (0 failures so far; the delta over the previously all-green head is one type-annotation line + a routine main merge).
- Drop the AI-attribution trailer — commit
9c3a8e146carriesCo-Authored-By: Claude Opus 4.8 …, which this repo blocks merges on. Amending/force-pushing to remove it will change the head SHA and dismiss this approval, so please re-request once it's clean.
The 'surfaces hard errors' test read the host-reported save meta synchronously after awaiting the error banner, but hasErrors reaches the host via a follow-up effect (onSaveMetaChange) that can lag the banner render. Under CI's loaded full-suite run this races, yielding hasErrors: false. Poll with waitFor instead. Surfaced on tinyhumansai#4917 because its Cargo.toml/lib.rs edits trip CI's full-suite vitest fallback; the test is otherwise latent-flaky on main. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
f8648f9
Summary
openhuman.voice_*RPC answersunknown method, so voice transcription is completely non-functional.openhuman_core = { ..., default-features = false }, which silently dropped the default-ONvoicefeature added by feat(core): compile-time voice feature gate (voice + audio_toolkit) (#4803) #4833. Fixed by optingvoiceback in explicitly.constassert so a voice-less desktop binary fails the build instead of shipping and failing silently at runtime.Problem
Since PR #1061 the core runs in-process inside the Tauri shell, so the shell's dependency declaration is what actually ships. That declaration has carried
default-features = falsesince the in-process move (app/src-tauri/Cargo.toml:135, dated 2026-05-16), back when the core crate had no[features] defaultlist at all — a harmless no-op.#4833 (implementing #4803) then made
voicea default-ON compile-time feature so slim/headless builds could drop it. That collided with the pre-existingdefault-features = false: the desktop build stopped enablingvoice, so the#[cfg(feature = "voice")]controller registrations insrc/core/all.rs(lines 210, 641) were never compiled in, and the wholeopenhuman.voice_*namespace became unknown-method.Because the gate is compile-time, restarting or reinstalling can never help — the code is absent from the binary. This is not a migration or data issue: a clean install of an affected release fails identically.
Evidence
hound— reachable only viavoice = ["dep:hound", "dep:lettre"]— was absent from the shell's dependency graph (cargo tree -i hound→ "did not match any packages") while present in the root crate's graph. Same probe, same worktree; the only difference isdefault-features = false.unknown method: openhuman.voice_status— 46,735 events / 56 usersunknown method: openhuman.voice_server_status— 46,592 events / 52 users2026-07-14T14:48:59Z, hours after feat(core): compile-time voice feature gate (voice + audio_toolkit) (#4803) #4833 landed (821bac2ee, 2026-07-14 06:57 +0530). First bad release0.58.19, still failing as of0.61.0.Note: the issue hypothesised a missing/mis-bundled
ggml-base-q5_1.bin. That is a red herring — the model is never reached, and thevoicegate does not drop whisper-rs (it lives in the inference domain).Solution
app/src-tauri/Cargo.toml— addfeatures = ["voice"], plus a comment recording thatdefault-features = falsepredates the core's defaults, so every default the core gains must be opted back in here.app/src-tauri/src/lib.rs—const _: () = assert!(openhuman_core::openhuman::voice::VOICE_COMPILED_IN, ...). Cargo features are per-crate, so the shell cannot#[cfg]on the core's features; asserting the core's own always-compiled facade is the only way to detect a stubbed build. Placed alongside the existingcompile_error!desktop-target guard.src/openhuman/voice/mod.rs— exposeVOICE_COMPILED_INfrom the always-compiled facade (cfg!(feature = "voice")), so dependents can assert which build they got.app/src/features/human/voice/sttClient.ts— throws a typedVoiceNotCompiledErrorcarrying a stableVOICE_NOT_COMPILED_CODE, replacing two duplicated inline strings. The English message is kept deliberately (it is what reaches debug logs and Sentry) and retains theunavailable in this buildsubstring, whichMicComposer'sPERMANENT_ERROR_PATTERNSmatches to suppress a retry/backoff loop that can never succeed against a compile-time gate.MicComposerpreviously spliced the raw English error into the translatedmic.transcriptionFailedtemplate ("Transcription failed: {message}"), so the other 13 locales rendered a half-translated sentence. It now branches on the error code and rendersmic.voiceNotCompiled, added to all 14 locales with real translations. Detection duck-types oncoderather thaninstanceof, since the error crosses async/retry boundaries where prototypes can be stripped.src/openhuman/voice/compile_status.rs—VOICE_COMPILED_INlives in a sibling module (AGENTS.md requiresmod.rsbe export-only), ungated so it exists in both gate states, with tests pinning it tocfg!(feature = "voice")rather than a literal so they hold for both builds.Scope note: the same line also drops
tokenjuice-treesitter(the core's other default). Not bundled here — the shell separately setstinyjuice = { default-features = false }, suggesting the tree-sitter C build may be avoided deliberately. Filed as #4918, with #4919 to close the whole class in CI.Merge note: the
mediagate (#4804/#4840) landed onmainmid-review and hit this same trap — its author forwardedmediaand documented the rule in a comment, butvoicestayed broken. The merge conflict was resolved by keeping both gates and merging the comments into one rule. Two of the core's three defaults have now been dropped at some point, which is the argument for #4919: a code comment is documentation, not enforcement.Submission Checklist
sttClient.test.ts/MicComposer.test.tsx(212 related tests pass).N/A: behaviour-only bug fix, no feature rows added/removed/renamed## RelatedN/A: no checklist change; voice smoke steps already covered. Verified manually on macOS (see Impact).Closes #NNNin the## RelatedsectionImpact
Runtime/platform: desktop (macOS/Windows/Linux). Restores the entire
openhuman.voice_*surface — transcription, TTS, availability checks, and the dictation server — all of which were absent from shipped builds.Verification
houndin shipped shell graph (proxy forvoice)error[E0080]with the guard message (red)cargo check, voice ONcargo check, voice OFF (--no-default-features --features tokenjuice-treesitter)tsc --noEmit, ESLint, Prettier,cargo fmt --checkpnpm dev:appon macOS, mic → transcript → sentBinary size: re-adds
hound+lettreto the desktop build. These were always intended to ship; their absence was the bug.Compatibility / migration: none. No data, schema, or config involved — purely which code is compiled into the binary. Users on 0.58.19–0.61.x remain broken until they update to a release containing this fix.
Related
Closes #4901
tokenjuice-treesitterstill un-forwarded (fails soft, no error)N/A — no coverage-matrix rows affected (behaviour-only fix)Summary by CodeRabbit
Bug Fixes
Localization
Tests