Commit Graph
23 Commits
Author SHA1 Message Date
Palash Debnath 08569397d3 fix(asr): secure configured endpoints and refresh guidance (#1751)
Refreshes README and linked docs with accurate installation, platform, privacy, API, and model-license guidance; documents local gigastt; and pins OpenAI-compatible ASR traffic to the configured secure origin. Closes #1736.
2026-09-02 04:41:36 +05:30
Palash Debnath da8358916c fix(desktop): preflight Linux runtime dependencies (#1681)
Fail before backend/window startup when Linux source hosts lack Enigo’s libxdo linker input or WebKit’s GStreamer audio sink. Print an exact distro package command, sync source-build docs, and lock the probes with deterministic tests.

Closes #1680
Closes #1682
2026-08-28 07:19:36 +05:30
Parker Hill 078bad8e0f fix(setup): repair fresh-clone desktop and ROCm source installs (#1664, #1665)
Fix fresh-clone desktop development by creating the required dist placeholder before Tauri starts, and make source setup install the selected CUDA or ROCm PyTorch stack consistently. Adds behavior-level cross-platform regression coverage.\n\nFixes #1664.\nFixes #1665.\n\nThanks @uberclokr for the contribution.
2026-08-27 18:33:55 +05:30
Palash Debnath 7640f42dce feat(install): one-command installer URL (.sh + .ps1) + 3-OS install smoke (#1627)
* feat(install): one-command installer URL (.sh + .ps1) + 3-OS install smoke

- scripts/install.ps1: Windows source installer (winget deps, uv, bun,
  clone, uv sync, frontend build); honors OMNIVOICE_PYTHON/OMNIVOICE_REGION
- infra/install-redirect: Cloudflare Worker serving /install with
  User-Agent sniffing (curl -> sh, PowerShell -> ps1, browser -> landing
  page); proxies live from main; /install.sh + /install.ps1 aliases
- scripts/install.sh: fix stale advertised URL (main/install.sh never
  existed) and repo-root resolution so a local run no longer clones a
  duplicate repo into ~/VoiceStudio (verified on macOS arm64)
- .github/workflows/install-smoke.yml: run both installers end-to-end on
  ubuntu/macos/windows when they change
- docs-sync: install one-liners lead each platform guide; STRUCTURE.md

(#1626)

* fix(install): don't let a failed bun download pass silently

curl | sh runs an empty script and exits 0 when the download fails, so
a bun.sh hiccup surfaced much later as 'bun: command not found' (seen
on the macos-latest smoke runner). Fetch to a temp file, verify, fall
back to npm -g bun when node exists, and die with the manual command.
Same post-install verification for uv.

* fix(install): UTF-8 BOM for install.ps1 + quiet-style changelog entry

- tests/scripts/test_uninstall_ping.py requires shipped PowerShell
  scripts with non-ASCII text to carry a UTF-8 BOM (Windows PowerShell
  5.1 mis-decodes otherwise); same treatment uninstall.ps1 already gets
- test_changelog_style caps entries at ~400 chars
2026-08-21 11:47:39 +00:00
Palash Debnath 7718a7a10b fix: cross-platform dictation delivery (#1610)
Makes dictation delivery, capture, recovery, model fallback, AEC, and localized status behavior reliable across macOS, Windows, and Linux.
2026-08-21 03:33:50 +00:00
Palash DebnathandClaude Opus 5 41722afe3b refactor(launchpad): quieter, borderless design refresh (#1515)
* refactor(launchpad): quieter, borderless design refresh

The launchpad carried decoration from an earlier direction: icon chips,
corner-hung count badges, a permanently visible filled arrow, uppercase
mono card titles, and a dotted stipple divider — plus a frame that had
been invisible since the app-wide border tokens were zeroed.

Rework it around what the borderless direction actually implies:

- Feature tiles get a whisper-faint surface instead of a dead frame, and
  read as three bands (bare glyph + count / title + arrow / description).
  `--card-hue` is spent sparingly — the glyph at rest, the surface, count
  and arrow only once raised. Titles move to sans sentence case; counts
  are plain tabular numerals. Lift softened 4px -> 2px, coloured glow ->
  neutral shadow, plus an explicit focus ring and a staggered entrance.
- Hero drops the boxed "646" pill and the filled A/B-Compare button for
  quiet type, with a hairline standing in for the separation.
- Section labels trade the dotted stipple for a single fading hairline;
  rows are transparent until hover and reveal "Open" on hover/focus (it
  stays in the DOM, so AT and keyboard always reach it).
- Hero, tiles, recent files, callout and project lists now share one
  1180px column — previously only the top half was capped, so lists ran
  edge-to-edge on a wide display while the deck stayed centred.

Two bugs found and fixed while doing it:

- Buttons that had `border border-solid border-transparent` removed fell
  back to the UA default border and rendered a visible 1px outline. They
  now carry `border-0` explicitly.
- `.lp-animate` used `animation-fill-mode: both`, so after the entrance
  it kept owning `transform` — and animation-origin declarations outrank
  normal ones, which silently killed the card hover lift. Now `backwards`,
  which still holds the from-state through the stagger delay.

Also drops CSS the page has not rendered since #904: the cursor-spotlight
layer, the breath ring, and the per-card waveform strip.

Verified with headless renders at 1600/1280/940 and the empty state.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(dictation): decode Wayland portal signals and show the capture pill

The GlobalShortcuts portal declares Activated/Deactivated as
(o session, s shortcut_id, t timestamp, a{sv} options). We decoded the
timestamp as u32, so zbus rejected every signal with

  Signature mismatch: got `(osta{sv})`, expected `(osua{sv})`

and the press was dropped as an invalid signal. Registration succeeded
and the desktop even reported the bound chord back, so the hotkey looked
wired up while doing nothing at all — on every Wayland compositor, for
the whole life of the feature (#1490). Decode the 64-bit timestamp, and
keep the 32-bit spelling as a fallback so a non-conforming portal
degrades to working rather than to silence.

With presses arriving, the second half of the failure showed: nothing
had shown the widget window since it became a hidden recorder host, so a
capture ran with no pill on screen — and a mic or Accessibility failure
rendered into a window nobody could see. Add show_dictation_pill, which
bottom-centres the capsule on the monitor under the pointer and shows it
without taking focus (Windows keeps SW_SHOWNOACTIVATE so paste still
lands in the user's document), and call it from the widget for every
state but idle. Wayland denies clients their own placement, so the
compositor picks the spot there; the pill still appears.

dispatch_dictation_capture now logs whether a press was emitted or
queued — a press that reaches Rust and produces nothing was otherwise
indistinguishable from one the compositor never delivered.

Tests: portal signals decode at both timestamp widths (the 64-bit case
fails before this change with the exact production error); pill
placement centres, respects a second monitor's origin, and clamps rather
than going off-screen; the widget shows for a state needing the user,
stays hidden while idle, and never shows for a press that arrives while
dictation is disabled.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore: sync in-progress workspace changes

Uncommitted work already in the tree, checkpointed so the branch matches
the local machine:

- Remote GPU workers: join-from-the-app flow, one-time secrets, QR join
  codes, a Compute control in the status bar, and the device-list
  Workers panel (#1516)
- Model Catalogue workspace, with Settings pointing at it
- Settings sidebar search and keyboard navigation
- Demo assets for dubbing, dictation and voice design, plus the scripts
  that render them
- Backend: validation-error handling, ASR request-path degradation, and
  the accompanying tests
- CHANGELOG entries for the above and for the Wayland dictation fix

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(tests): follow Engines to the Model Catalogue, and green the sweep

- test_supertonic3 asserted the license gate points at "Settings" while
  the engine now names Model Catalogue → Engines, which is where the
  accept button actually lives. The assertion follows the move; what it
  pins is unchanged — the hint must name a place the user can reach it.
- Carries the CJK allowlist entries for the rendered dub bundle (#1517)
  and the regenerated route snapshot for /workers/agent (#1516), both of
  which this branch inherits from the workspace sync.
- docs/install/linux.md: the dictation capsule is bottom-anchored
  everywhere except Wayland, where the protocol gives applications no
  say in their placement. Documented rather than left as a surprise
  (CodeRabbit).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* ci: stop a flaky dependency fetch from failing green runs

en-core-web-sm resolves to a direct GitHub release URL, and github.com
intermittently answers `http2 error: refused stream before processing
any application logic`. uv's own three retries all land within the same
few seconds and fail together, so the whole job dies on a dependency
that has nothing to do with the change under test — it cost #1518 and
#1517 an otherwise-green run tonight.

Two changes: back off between whole `uv sync` attempts, which is what
actually clears it, and pass --no-sync to the pytest steps. `uv run`
re-resolves the environment before running, so every test step was a
fresh chance to hit the same fetch even though the install step had
already synced — that is exactly how #1518 failed, in the isolated
backend/tests step, with all 5467 tests already passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* ci: one retry seam for every uv sync, not just the job that failed last

en-core-web-sm resolves to a direct GitHub *release* URL rather than a
package index, and github.com intermittently answers `http2 error:
refused stream before processing any application logic`. uv's own
retries all land inside the same ~10 seconds and fail together, so a job
dies on a dependency unrelated to the change under test. Tonight that
cost four otherwise-green runs across #1515, #1517 and #1518 — and the
first fix only covered the Tests job, so the next failure simply moved
to Smoke (Linux), which syncs separately.

The fetch is per-job, so the fix has to be per-job: scripts/uv-sync-retry.sh
backs off between whole attempts (15s, 45s, 90s) and every workflow that
syncs now goes through it — ci.yml (tests + the platform matrix),
release.yml, security.yml, evals.yml. It still fails loudly after four
attempts, so a genuinely broken lockfile is not disguised as a flake.

The Tests job also lacked the UV_HTTP_TIMEOUT / UV_HTTP_RETRIES the smoke
matrix has always set, which is part of why it was the one that kept
dying; it has them now.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(ci): pin the Intel-Mac contract by intent, not by command spelling

test_ci_verifies_intel_mac_as_the_documented_remote_only_host asserted
the literal line `run: uv sync --extra pockettts`, so routing every sync
through scripts/uv-sync-retry.sh read as a broken Intel-Mac contract. The
contract it exists to protect is that the pockettts extra installs ONLY
on backend_supported legs — which the regex now pins, while leaving how
the sync is invoked free to change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* ci: keep every uv run out of the resolver, and bound the retry budget

CodeRabbit, #1517:

- `uv run` re-resolves before running, so the smoke suite, the
  worker-artifact tests, the release test run and the eval run were each
  a fresh chance to hit the flaky direct-URL fetch outside the retry
  loop. All of them pass --no-sync now; the environment is already
  synced by the step that owns the retries. security.yml's
  `uv run --with pip-audit` is deliberately left alone — it layers an
  ephemeral package rather than running the project's own tests.
- The retry count multiplied uv's own budget (UV_HTTP_RETRIES=5 with a
  120 s timeout on the smoke matrix). Three attempts and 60 s of total
  backoff outlast the refusals actually observed while staying well
  inside the jobs' timeout-minutes.
- The Intel-Mac contract test pinned the smoke command literally too, so
  --no-sync tripped it exactly like the sync line did. Same fix: assert
  the contract (smoke runs only on backend_supported legs), not its
  spelling.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 23:46:07 +00:00
debpalash cedd038e3e fix(dictation): make capture reliable across desktops 2026-08-11 11:48:01 +00:00
debpalash 7eec1d7d11 fix(dictation): make shortcuts truthful across desktops 2026-08-11 05:49:51 +00:00
Palash Debnath 7cde2fcd06 fix(desktop): make recording and dubbing reliable (#1481)
* fix(ui): keep scaled desktop shell responsive

* fix(linux): support desktop microphone capture

* fix(ui): update the centered VoiceStudio brand

* fix(audio): fall back when recorder start is unsupported

* fix(desktop): use the app header as titlebar

* feat(audio): add live microphone input controls

* fix(dub): recover from missing transcription models

* fix(dub): make pipeline stages actionable

* fix(asr): recover low-memory transcription

* docs: record desktop reliability fixes

* fix(ui): use semantic error banner border

* fix(dub): harden recovery and recording fallbacks
2026-08-10 22:43:37 +00:00
Palash Debnath 5cab8e0149 feat: rename the product to VoiceStudio (previously OmniVoice-Studio)
Renames what users see. The app, the installers, the window title, the
docs and all 21 locales now say VoiceStudio, with "(previously
OmniVoice-Studio)" noted near the title of each doc surface so people
recognise it.

Deliberately NOT renamed, because renaming any of them silently breaks
an existing install — there is no legacy-path fallback anywhere in this
codebase:

  - bundle identifier com.debpalash.omnivoice-studio (MSI UpgradeCode,
    macOS TCC grants, managed venv, WebView localStorage, the
    single-instance lock)
  - data directories OmniVoice / .omnivoice and omnivoice.db
  - the ~150 OMNIVOICE_* environment variables
  - the X-OmniVoice-* HTTP headers (a wire protocol)
  - the published Docker image paths
  - the OmniVoice ENGINE, which is a model name and not this product

tests/test_identity_paths_survive_the_rename.py pins every one of those
so a future well-meaning sweep cannot orphan a user's library.

Linux .deb users install a new package name and should apt remove
omnivoice-studio; that note is in the changelog.
2026-08-07 01:30:58 +05:30
Palash Debnath a23e69d014 chore: point every repo reference at github.com/debpalash/VoiceStudio (#1394)
The repository was renamed. 724 references across 59 files now point at the new URL — README badges, docs, install guides, the updater's releases API call, CONTRIBUTING, the Colab link and the probe harness. GitHub redirects the old URLs, so nothing was broken in the meantime.

Deliberately NOT renamed, because each breaks something on a user's machine: the Tauri bundle identifier (the path to every existing user's data), /usr/lib/omnivoice-studio and the compose container names, and the published Docker image paths.

The image path needed a code change to STAY still: docker.yml derived it from github.repository, so the next build would have published to ghcr.io/debpalash/voicestudio while Docker Hub, a hardcoded literal, stayed put — everyone pulling the documented GHCR path would have kept receiving the last pre-rename image forever. It is now pinned, with a test that fails if it ever derives from the repo name again.

Also makes the probe's repo-name assertion shape-based: it hardcoded the old name and failed on every PR after the rename while the code it tests worked perfectly.
2026-08-06 18:15:56 +05:30
Palash DebnathandClaude Opus 5 810b598739 fix(appimage): let the host GStreamer win, and stop sharing its registry (#1333) (#1354)
* fix(appimage): let the host GStreamer win, and stop sharing its registry (#1333)

Recording from the AppImage failed with "No microphone found" on a Debian
13 host whose audio stack the reporter verified healthy (pactl, wpctl,
gst-launch with both pulsesrc and pipewiresrc), while the same build`s raw
binary recorded fine. GST_DEBUG=2 named it:

  WARN GST_REGISTRY gst_registry_binary_check_magic:
    Binary registry magic version is different : 1.23.90 != 1.3.0
  GStreamer element appsink not found. Please install it.

linuxdeploy bundles libgstreamer-1.0 because WebKit links it, but not the
plugins: those are dlopen`d, so nothing static can see them to copy. The
bundled core falls back to the host plugin directory, whose plugins were
built against the host core, the version check rejects them, and the scan
yields nothing. appsink is one of the casualties and it is the element
WebKit hands a capture stream to, so getUserMedia() rejects NotFoundError.

Same class as #1258 (frozen bundled library against a host that moved on)
in a different library, which is why OMNIVOICE_PREFER_SYSTEM_WEBKIT=1 did
nothing for the reporter. Since we ship no plugins, the host core is the
only one that can agree with the plugins that will load — so prefer it,
with OMNIVOICE_PREFER_SYSTEM_GSTREAMER=0 as the escape hatch.

Also isolate the registry cache. GStreamer keys ~/.cache/gstreamer-1.0/
registry.<arch>.bin by architecture alone, so two cores of different
versions clobber each other`s file: that makes the failure depend on which
app ran last, and the AppImage corrupts the cache for every other
GStreamer app on the machine. Both directions go away with a private path.

AppRun.test.sh covers host-present, host-absent and opt-out; all three
fail against the previous AppRun.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(appimage): cover the ldconfig discovery path; docs fixes

CodeRabbit, all three valid:

- every GStreamer case forced ldconfig to fail, so the runtime-only-host
  fallback (no -dev package, hence no .pc file) was never exercised. The
  cases now select their discovery path, and the new ldconfig one fails if
  that branch is removed.
- MD040: the GST_DEBUG fence had no language tag.
- the registry cache path follows XDG_CACHE_HOME when set; ~/.cache is only
  the default. Documented, along with WHY the shared file is a problem.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(appimage): compose LD_LIBRARY_PATH once; host WebKit stays first

CI caught a real regression, not a flaky test. The GStreamer block prepended
its own directory, which put it AHEAD of the host WebKit dir — and "host
WebKit first" is the invariant #1258 turns on. On a host where the two
libraries live in different directories that silently changes which WebKit
resolves.

It only showed on Linux because the WebKit ldpath cases do not stub away a
real host GStreamer, so the runner had one to find and macOS did not.
Reproduced locally with an ldconfig shim, and confirmed the ordering is what
fixes it: with the old order the suite is 19/2, with this one 21/0.

Both decisions now compose one path in one place — host WebKit, host
GStreamer, bundle, inherited — so neither preference is weakened and the
ordering is stated where it is applied rather than implied by two
independent prepends. Same directory for both (the common case) is not
listed twice.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(appimage): preload the host GStreamer instead of hoisting its libdir

greptile P1, valid. The host GStreamer lives in a general system library
directory (/usr/lib/x86_64-linux-gnu on Debian), so putting that directory
ahead of ${HERE}/usr/lib replaced EVERY other bundled library with the
host copy — loader symbol errors, startup crashes, or a blank window on a
distro we never built against. One library needs to come from the host and
the mechanism has to be that narrow.

LD_PRELOAD names exactly that library and leaves the search path alone, so
the WebKit ordering from #1258 is untouched too (and this removes the
composed-LD_LIBRARY_PATH block that only existed to keep the two
prepends from fighting). The preload is inherited by the Python backend,
where nothing links GStreamer and it is inert — the accepted cost.

Tests now assert both halves: the library IS preloaded, and the libdir is
NOT hoisted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(appimage): verify the host GStreamer loads before preloading it

greptile P1, valid. The host core links GLib and the bundle ships GLib too,
resolved bundle-first — so a host GStreamer built against newer GLib than
we bundle fails its relocations and the app does not start at all. That is
strictly worse than the broken microphone this PR fixes. Taking host GLib
as well is not an option either: GLib is what WebKit is built against, so
pulling it from the host reopens #961/#1258.

Rather than predict the pairing, test it. The loader processes LD_PRELOAD
for any binary, so running `true` under the exact environment the app will
get is a complete check of whether the library loads there — a missing
dependency or an unresolved version tag ("version GLIB_2.84 not found")
fails it and nothing else runs. On failure the preload is skipped, the app
starts on the bundled core, and a warning names the mismatch so the user
has a thread to pull rather than a silent half-fix.

OMNIVOICE_APPRUN_PRELOAD_PROBE lets the suite choose the outcome, matching
the existing OMNIVOICE_APPRUN_WK_MARKER precedent; the new case fails if
the guard is removed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 12:48:47 +05:30
Palash Debnath 15afc6611d fix(linux): AppImage blank window on Mesa 26.1+ hosts (#1258, #1244) (#1265)
* fix(linux): AppImage blank window on Mesa 26.1+ hosts (#1258, #1244)

The AppImage bundles an Ubuntu-built WebKitGTK but ships no libEGL, so that
bundled WebKit runs against the HOST's Mesa. On Mesa >= 26.1 it calls
eglGetPlatformDisplay() in a way the newer driver rejects and the app dies
before it paints:

    Could not create default EGL display: EGL_BAD_PARAMETER. Aborting...

No environment variable helps, because the failure is in EGL display creation
— before WebKit consults any rendering-path flag. #1258 confirmed
WEBKIT_DISABLE_DMABUF_RENDERER, WEBKIT_DMABUF_RENDERER_FORCE_SHM,
WEBKIT_SKIA_ENABLE_CPU_RENDERING, EGL_PLATFORM=surfaceless and
MESA_LOADER_DRIVER_OVERRIDE=swrast all fail identically.

Chasing the build runner's WebKit (#961 bumped 22.04 -> 24.04) cannot fix this
class: what we bundle is frozen and host Mesa keeps moving. So when the host
has a WebKitGTK at least as new as ours, let it win — the bundle still fills
every gap, and a host without WebKitGTK is untouched. That is exactly why
building from source works on the hardware where the AppImage does not.

The compositing workaround is re-decided against whichever library ends up
running, and AppRun.test.sh — which had never been wired into CI — now runs
there, so this logic stops being a regression test nothing executes.

* fix(review): the ordering change was a no-op; name the host libdir explicitly

CodeRabbit Major — correct, and it made the whole fix inert. LD_LIBRARY_PATH is
searched AHEAD of the linker's default paths no matter where in that variable a
directory sits, so on a normal launch (empty LD_LIBRARY_PATH) the bundle
remained the only explicit search directory and still won. Merely appending it
changed nothing. The host's WebKit libdir is now named explicitly, ahead of
ours. The new tests fail 3/3 against the previous version.

Greptile P1 — a host with the runtime but no -dev package has no .pc file, so
pkg-config can't answer and the check rejected a perfectly good system WebKit.
The libdir probe now falls back to ldconfig, and OMNIVOICE_PREFER_SYSTEM_WEBKIT
gives those users an explicit opt-in (=0 opts out) rather than gambling on an
unverified version, which would risk the #961 regression.

CodeRabbit — my changelog script had also inserted the CI entry into the
published 0.4.0 section. Removed; it belongs only under Unreleased.

CodeRabbit — the docs' source-build fallback used 'cd frontend', not the
repo-root flow the rest of the page documents. Fixed.
2026-07-29 15:36:45 -07:00
99e01610bb feat(docker): publish ROCm/AMD GPU image variant (#1165) (#1166)
The Docker image was CUDA-only, so AMD GPUs (e.g. RX 7900 XTX under
Podman) silently ran on CPU. Every preview and release now also ships a
ROCm variant built from the same Dockerfile:

- deploy/Dockerfile: parameterize the runtime base with a BASE_IMAGE
  build-arg (default unchanged: pytorch/pytorch 2.8.0 CUDA). Add
  PIP/UV_BREAK_SYSTEM_PACKAGES for the ROCm base's PEP-668-marked
  Ubuntu 24.04 Python (no-op on the conda CUDA base), and a build-time
  GPU_FLAVOR guard asserting the dependency install did not clobber the
  base image's GPU torch/torchaudio — a future dep bump that forces a
  torch reinstall now fails the build instead of shipping a CPU-only
  "ROCm" image.
- .github/workflows/docker.yml: new build-and-push-rocm job (separate
  job for runner disk — the ROCm base is ~25 GB unpacked, so it frees
  the preinstalled toolchains first). Tags mirror the CUDA semantics
  with a -rocm suffix (:rocm rolling preview, :stable-rocm, :X.Y.Z-rocm,
  :X.Y-rocm, :sha-xxxx-rocm) on both GHCR and Docker Hub, same secret
  gating. flavor latest=false so release tags can't clobber :latest.
  No cache-to: the ROCm layers would blow the 10 GB GHA cache budget.
- deploy/docker-compose.yml: new opt-in 'rocm' profile passing the GPU
  through via /dev/kfd + /dev/dri, with HSA_OVERRIDE_GFX_VERSION=11.0.0
  documented (user-set, not baked in — backend auto-sets it for known
  consumer GFX IDs).
- Docs-sync: docker.md (ROCm quick start incl. Podman/Quadlet, tag
  table, troubleshooting), dockerhub-overview.md, README AMD note,
  linux.md ROCm section cross-link, CHANGELOG [Unreleased].

Base image: rocm/pytorch:rocm7.2.4_ubuntu24.04_py3.12_pytorch_release_2.8.0
— torch 2.8.0 exactly matches the CUDA image (identical resolution, so
uv keeps it), py3.12 satisfies requires-python >=3.11 (the ubuntu22.04
variants are py3.10 and do not).

Closes #1165

Co-authored-by: mergetest <nizam4103@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 15:34:28 +05:30
5562aa16a7 feat(setup): media tools become invisible — bundled by default, controllable in Settings (#1071)
Most users should never learn what ffmpeg is. The Setup Wizard's SYSTEM
PREFLIGHT stops listing FFmpeg / FFprobe / yt-dlp as user-installed
requirements ("brew install ffmpeg…"): they are internal dependencies the
app provisions for itself. Genuine user facts (OS, RAM, disk, GPU,
network, Python) are untouched.

Backend
- New services/media_tools.py: per-tool status {version, path, origin:
  sidecar|bundled|system|custom}; background acquisition of a pinned,
  SHA-256-verified static ffmpeg+ffprobe build (immutable-commit fetch
  from the same upstream the static-ffmpeg pip package uses — that
  package itself was audited and rejected: mutable raw/main URL, no
  checksums, writes into site-packages); binaries are `-version`-probed
  via the existing _binary_runs before being trusted, installed under
  DATA_DIR (update-surviving, frozen-build-safe), zero new Python deps.
- ffmpeg_utils resolution chain gains the acquired-bundled tier — and
  ffprobe finally has a bundled tier at all (imageio-ffmpeg ships none),
  closing the source-install gap.
- New /media-tools router (loopback-gated, same contract as
  /system/set-env): status, acquire, {tool}/custom-path | use-system |
  restore, ytdlp/update | restore. Overrides persist via the existing
  env.FFMPEG_PATH / env.FFPROBE_PATH prefs convention — one store, no
  competing controls.
- yt-dlp updates: audited in-venv pip/uv upgrade and rejected (venv is
  uv-managed with no pip; yt-dlp is a locked dep, so the updater's
  --inexact drift sync would revert it). Instead the newest wheel —
  verified against PyPI's own sha256 — lands in a DATA_DIR overlay
  prepended to sys.path at startup: survives app updates, works in
  frozen builds, and "Restore tested version" is just deleting the
  overlay. Gallery now runs yt-dlp via `python -m yt_dlp` (module, not
  PATH) so the CLI can never be a user-install task either.
- /setup/preflight drops the three tool rows, carries a media_tools
  verdict, and self-heals: kicks the bundled download in the background
  when no tier resolves (never re-fires after a failure — the wizard's
  card owns Retry). diagnose + the ffmpeg-missing notification now point
  at Settings → Audio tools instead of package managers.

Frontend
- Wizard: new MediaEngineCard — renders NOTHING when the engine is ready,
  a one-line progress while acquiring, and only on failure an actionable
  card (Retry / Use a system copy / Choose file…).
- Settings → Audio tools (new category, System group): FFmpeg + FFprobe
  rows with version, path, origin badge, Use system copy / Choose file… /
  Restore bundled, header-level "Update bundled build"; yt-dlp row with
  Update + Restore tested version (+ restart affordance). Package-manager
  commands appear only as copyable prose, never executed.
- The FFmpeg-path override moved out of Settings → Network (pointer row
  deep-links to Audio tools; no second writer of env.FFMPEG_PATH).
  Notifications gain a settings-tab action type.
- All strings i18n (en + defaultValue), a11y labels on every control.

Tests: 29 new backend (origin classification, checksum/size/probe
rejection, override persistence, overlay update/restore, router gating +
route-shadowing) + preflight contract tests (tool rows gone, verdict
present, auto-acquire fires once); 14 new frontend (wizard hide/progress/
failure-card, Audio tools rows/badges/actions). Route snapshot
regenerated. Docs (macos/linux install, troubleshooting §7b) describe the
new reality in the same commit.

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 12:22:49 +05:30
93aab6dadc docs(linux): mention yt-dlp as an optional prerequisite (#973) (#997)
The preflight system check already warns in-app when yt-dlp is missing
(Voice Gallery/Dub YouTube downloads fail without it), but the install
docs never mentioned it — a user has to hit the in-app warning first
instead of seeing it up front alongside the other optional prereqs.

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 04:42:00 +05:30
dab6456581 docs(linux): stop advertising a .deb package that isn't published (#990)
README's Quickstart badges linked a 'Download Debian .deb' button
straight to the releases page — but .deb bundling was deliberately
dropped from release.yml (tauri-cli bug, 'Failed to create control
scripts') and no release has ever shipped one. A community member
investigating #961 confirmed this by checking the actual release
assets. Users clicking that badge got a broken promise, not a package.

Removed the badge; docs/install/linux.md's '## Install (.deb)' section
now honestly states it's unavailable pending a tauri-cli fix, points to
the AppImage as the supported path, and keeps the historical pre-v0.3
.deb upgrade note (ffprobe conflict) since that's still relevant to
existing installs.

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 04:25:40 +05:30
1a03c59f82 fix(install): AMD ROCm torch reinstall targets rocm6.4, not rocm6.2 (#988)
Community-diagnosed (issue #972, Kaihui-AMD): pyproject.toml pins
torch==2.8.0, but the rocm6.2 wheel index only ever published up to
2.5.1 — the reinstall silently failed to resolve and fell back to the
default CUDA build, which runs on CPU on an AMD GPU. The failure was
correctly logged (bootstrap.rs's emit_log warning), just never actioned
because the index itself couldn't succeed. rocm6.4 carries a matching
torch==2.8.0 build.

Docs updated with the corrected index plus a repo.radeon.com find-links
path for users who want a driver-matched ROCm 7.2.x build the PyTorch
index doesn't carry (OMNIVOICE_TORCH_INDEX only accepts a PEP 503 index,
not find-links, so that's documented as a manual step).

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 04:25:00 +05:30
4aa9abe22a docs+scripts: install fixes — desktop-prod tauri resolution, Ubuntu white-screen guidance, honest GPU/prereq docs (#960 #961 #962) (#964)
* docs+scripts: install fixes — desktop-prod tauri resolution, Ubuntu white-screen guidance, honest GPU/prereq docs (#960 #961 #962)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(changelog): add the install-fixes batch under [Unreleased] (#964)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 19:34:01 +05:30
Deepak VishwakarmaandClaude Opus 4.8 de3d83f14b docs: add rust as prerequisite for from-source builds (#704)
Adds Rust/Cargo as a from-source build prerequisite across the linux/macos/windows install docs. Thanks @Deepakv2104.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 03:03:23 +05:30
Antony-hash512andgreptile-apps[bot] 506fa8ea49 Add Arch Linux installation instructions (#209)
* Add Arch Linux installation instructions

Added installation instructions for Arch Linux.

* Update docs/install/linux.md

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-06-01 08:08:45 +05:30
Palash DebnathandClaude Opus 4.8 79473c4c01 docs(#124): document AMD GPU (ROCm) install path (#151)
Detection already works (get_best_device + HSA_OVERRIDE_GFX_VERSION); the
gap was that the default install ships CUDA torch, so AMD users fell back
to CPU with no guidance. Document the opt-in ROCm wheel swap (rocm6.2),
the device-verify one-liner, and the HSA override for unsupported GFX.

Linux-only, opt-in — default cross-platform behavior unchanged. An
installer-integrated env-var-driven wheel selection is a tracked follow-up.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 19:20:23 +05:30
Palash DebnathandClaude Opus 4.7 715766cb04 Phase 1 Wave 2: per-OS install docs + Settings UI + error→docs deeplinks (#94)
* docs(install): per-OS install pages + drift validator + CI gate

Splits the 600-line README install section into self-contained per-OS docs
under docs/install/{macos,windows,linux,docker}.md plus a Top-10
troubleshooting index. Each OS doc is end-to-end: a user opens it and
reaches a working app following only commands inside that file.

Adds:
- docs/install/{macos,windows,linux,docker}.md  (OS-specific install paths)
- docs/install/troubleshooting.md               (top 10 install errors)
- docs/engines/cosyvoice.md                     (closes #55 docs half)
- docs/features/diarization.md                  (pyannote license flow)
- docs/setup/huggingface-token.md               (3-source cascade guide)
- scripts/validate-install-docs.py              (INST-06 docs-drift gate)
- tests/scripts/test_validate_install_docs.py   (B-5: validator self-tests)
- .github/workflows/ci.yml step running the validator on every PR

Implements INST-02 (README routing), INST-03 (macOS Gatekeeper anchor),
INST-12 docs half (Windows torch-compile-oom anchor), DOCS-01..05.

The validator is a one-way diff: every `<!-- validate -->`-tagged line
in docs must appear in scripts/desktop-prod.sh after normalisation
(prompt-prefix strip, CRLF, trailing whitespace, blank-and-comment skip).
A `<!-- validate: skip -->` marker opts out for human-readability blocks.
Its own 10 unit tests catch regressions in the gate itself.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(deeplinks): links.py + error_docs_map (Python + TS mirror)

Adds the single source of truth for the project repo URL and the 4-class
error → docs taxonomy that both the in-app ErrorBoundary deeplink button
(Wave 2 Task 3) and the Phase 5 bug reporter will consume.

New:
- backend/core/links.py            — PROJECT_REPO_URL + BLOB_MAIN resolver
                                      (Tauri config first, pyproject fallback)
- backend/core/error_docs_map.py   — lookup(error_class) → docs URL
- frontend/src/utils/errorDocsMap.ts (TS mirror with classifyError helper)
- tests/backend/core/test_links.py + test_error_docs_map.py
- frontend/src/utils/errorDocsMap.test.ts

Resolves checker B-6 (links.py ownership) and Open Question #3 (which fork
the deeplinks resolve to — the Tauri updater endpoint wins, which points
at the desktop app fork debpalash/OmniVoice-Studio).

The TS BASE constant is documented as the second hardcoded URL drift site;
the keys-sync test (`test_keys_match_python_map` equivalent) guards the
4-class taxonomy contract between Python + TS halves.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(ui): Settings → API Keys panel + ErrorBoundary docs deeplink

Wave 2 AUTH-03 UI half + ErrorBoundary deeplink wiring.

ErrorBoundary fallback now renders an "Open docs for this error" button
that classifies the thrown Error message (heuristic: pkg_resources → 401 /
HfHubHTTP → WebKit / white screen → quarantine / Gatekeeper) and opens the
matching docs anchor via Tauri shell.open (with a window.open fallback
in browser dev mode).

ApiKeysPanel consumes the Wave 1 resolver state endpoint:
  - 3 source rows (App / Env var / HF CLI) with set/unset indicator,
    masked token preview, whoami username + green check
  - "Active" badge on whichever source is currently serving the cascade
  - App-row only: Save (POST /api/settings/hf-token) +
    Clear (DELETE with optional "also clear HF CLI" confirm dialog)
  - "Test now" button refetches state (invalidates the resolver's
    validation cache via the same endpoint hit)

Panel mounted in the existing Settings → Credentials tab; the legacy
HF_TOKEN row from CREDENTIAL_FIELDS is filtered out so the two paths
don't fight over the same key.

Threat T-02-02: the panel never displays the full token. The masked
value comes from the resolver state endpoint; the full token only
crosses the IPC boundary on Save (POST) and is cleared from local
state on success.

Closes AUTH-03 fully (Wave 1 backend + this Wave 2 UI).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(perf): INST-12 Disable torch.compile (Windows) toggle (backend + UI)

Wave 2 Task 4 — full INST-12 delivery per checker B-2/B-7 v0.3.0 fat-release
decision. Both the docs half (windows.md anchor, shipped in earlier commit)
and the runtime toggle are now in Phase 1.

Backend:
- backend/services/settings_store.py: adds get_text/set_text helpers for
  non-secret config (refuses to write to the encrypted hf_token key).
- backend/api/routers/settings.py: GET + PUT
  /api/settings/perf/torch-compile-disabled, both under the existing
  loopback guard (threat T-02-04).
- backend/services/engine_env.py: new `build_engine_env()` helper that
  centralises HF_TOKEN/YOUR_HF_TOKEN injection from the 3-source resolver
  AND injects TORCH_COMPILE_DISABLE=1 when the flag is set on win32.
  Phase 2 SubprocessBackend launchers should adopt the same helper.
- backend/services/sonitranslate.py: migrated to engine_env.build_engine_env()
  while preserving the source-level `env["HF_TOKEN"]` sentinel that
  test_sonitranslate_module_uses_resolver checks.

Frontend:
- frontend/src/components/settings/PerformancePanel.{jsx,css,test.jsx}:
  toggle UI with the explainer for #65; renders disabled with a "not
  applicable" badge on macOS/Linux.
- frontend/src/pages/Settings.jsx: mounts the panel into the Credentials
  tab alongside the API Keys panel.

Tests:
- tests/backend/test_perf_settings.py: 7 backend tests (default state,
  PUT persistence, T-02-04 non-loopback rejection, settings_store round-
  trip, env injection on win32, NO injection on macOS/Linux, NO injection
  when disabled).
- frontend PerformancePanel.test.jsx: 5 tests (renders from GET state,
  PUT on toggle, disabled on non-Windows platforms, pre-enabled state).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(planning): Wave 2 SUMMARY + REQUIREMENTS status updates

- .planning/phases/01.../01-02-SUMMARY.md: full implementation report
  per template (truths, commits, tests, deviations, drift-site
  acknowledgments per W-3, launcher seam name for Phase 2,
  taxonomy keys for Phase 5).
- .planning/REQUIREMENTS.md: flips Wave 2 closures to Done:
    AUTH-03, INST-02, INST-03 (docs half), INST-06, INST-12,
    DOCS-01..05.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 06:22:10 +05:30