fix/server-mode-admin-auth
87
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
cedd038e3e | fix(dictation): make capture reliable across desktops | ||
|
|
7eec1d7d11 | fix(dictation): make shortcuts truthful across desktops | ||
|
|
95a35b8e07 |
feat(indextts): add native IndexTTS 2.5 support (#1485)
* feat(indextts): add native 2.5 sidecar support * fix(indextts): preserve legacy language metadata * docs(indextts): state model license terms accurately * fix: preserve IndexTTS upgrades and duration controls * fix: complete IndexTTS upgrade safeguards |
||
|
|
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 |
||
|
|
501450e8ec | Merge remote-tracking branch 'origin/main' into codex/pr1442 | ||
|
|
1a71fbea72 | Merge remote-tracking branch 'origin/main' into fix/1274-docker-python-runtime | ||
|
|
f8f992ed9c | Merge main and address Docker review findings | ||
|
|
3b808ed20d | fix: harden cookie import lifecycle | ||
|
|
25c84da606 |
Merge remote-tracking branch 'origin/main' into fix/1274-docker-python-runtime
# Conflicts: # CHANGELOG.md |
||
|
|
74e6d4582b | fix(dub): keep cookie credentials off remote plaintext HTTP | ||
|
|
034dd2333d | fix(pockettts): complete first-use terms gate and smoke | ||
|
|
b1b18dd1ba | fix(dub): support explicit YouTube cookie exports (#1429) | ||
|
|
b991e59a0f | fix(docker): keep ROCm runtime on guarded Python | ||
|
|
1eb59c6f18 |
feat(diagnostics): say why a GPU host fell back to CPU (#1274) (#1425)
The About page reported 'Compute device: cpu / GPU active: no / VRAM 0.00 GB' on a machine with a working GPU. Every line was true and none was usable — it is also exactly what a machine with no GPU at all reports, so the report could not distinguish a driver that isn't loaded from a container that cannot open the device from a ROCm older than the card. The probe already knew all of it; torch.cuda.is_available() returning False simply produced no note. Each cause now reads differently: a missing device node names the --device flags, a permissions failure names --group-add and how to find the host's real render/video GIDs (copied numbers are the most common way this ends up on CPU in Docker), a card newer than the shipped ROCm points at rocminfo, an HSA_OVERRIDE_GFX_VERSION that is doing more harm than good is named first because it is both likelier and cheaper to test, and an unreachable NVIDIA driver gets its own advice. The probe never diagnoses from a measurement it did not complete: when torch.cuda.is_available() itself raises, the exception is reported and no device findings are asserted beside it. Metadata access that raises is contained too — this runs on the path whose whole job is to explain a failure, so it cannot become one. |
||
|
|
0c82167839 |
feat(firstrun): let the user choose the portable folder (#766)
Portable mode put everything in OmniVoiceStudio-Data beside the app — the only storage row on the setup screen you could look at but not change, while installed mode had a picker for all three of its directories. The pin had a reason: portable_base() is computed from the executable's location and never read from config, which is what makes a portable install self-discovering. A user-chosen folder breaks that, because the only record of the location would live inside the folder being located. So it is recorded somewhere findable, in order: a portable.path marker beside the app; then portableDir in the per-user config for app folders that are read-only; then the historical default, so existing portable installs resolve byte-identically. The marker stores a RELATIVE path whenever the folder sits inside the app's own directory — the USB-stick case portable mode exists for, where app and folder move as a unit and the mount path is free to change. Anywhere else only an absolute path can be stored, and the setup screen says the install is tied to it rather than promising portability it cannot keep. Two traps worth naming, both caught in review: the per-user fallback would recurse forever through load_config (config_path to portable_config_file to portable_base), so it reads the platform config file directly; and clear_portable_dir was reading through that same chain, silently failing to clear the machine record so the old folder kept winning after the user chose the default. This also retires the greyed-out Portable option after a default Program Files install (#766) — you point it at a writable disk and get the machine-bound variant. Rust 109 passed, frontend 1705 passed. All 21 locales in lockstep; docs/install/windows.md rewritten. |
||
|
|
43d3537adf |
fix(asr): stop a missing cuDNN 8 from killing the backend process (#1371)
CTranslate2 (the engine under WhisperX and faster-whisper) requires cuDNN 8 while torch ships cuDNN 9. When the side-loaded cuDNN 8 is absent it does not raise — it __fastfail()s, killing the whole backend with 0xC0000409 and no traceback, so the shell restarts it and the next attempt dies the same way. The defect was that the backend computed the answer and discarded it: the preload passed silently on a missing directory and on every OSError, then called into a library that treats the same condition as fatal. The answer is now kept, and the CTranslate2 engines report themselves unavailable so auto-detect falls through to pytorch-whisper. Two more instances of the same class went with it: the crash-isolated ASR sidecar is a child process that never preloaded at all (failing every transcribe, quietly), and the preload only searched <project root>/.venv, missing any other interpreter. Conservative by design — a false positive costs WhisperX's forced alignment, so ROCm is excluded via torch.version.hip before cuda.is_available(), which is True on HIP builds. 14 regression tests; backend suite 4374 passed. |
||
|
|
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.
|
||
|
|
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. |
||
|
|
d23e56a5ec |
fix(errors): the transformers-import advice names torchvision now (#1376) (#1377)
The TRANSFORMERS_IMPORT hint and the ASR pipeline error told users to reinstall torch + torchaudio + transformers. torchvision — the package whose ABI mismatch actually produces this exact lazy-import wording (#1357's torchvision::nms, wrapped into "Could not import module 'AutoFeatureExtractor'") — was the one package the advice omitted. Following it to the letter left the broken package untouched (#1376). Both surfaces now name the mismatch as a cause and prescribe the pinned reinstall with literal versions (desktop installs ship no deploy/, so the constraint-file form fails there) targeting the venv explicitly. A lockstep test asserts the exact command on every advice surface against deploy/torch-constraints.txt, so a pin bump stays red until the advice matches. Docs gain the same-wording-different-cause section (1a-bis). |
||
|
|
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> |
||
|
|
168e8e5c61 |
fix(macos): declare the floor the app actually delivers (13.3, not 12) (#1314)
* fix(macos): declare the floor the app actually delivers (13.3, not 12) The app declared minimumSystemVersion 12.0 and the docs promised Monterey, while the frontend required Safari 16.4 in three independent places: Vite's default build target (baseline-widely-available = safari16.4), Tailwind v4's own documented floor, and `@property` throughout its generated utilities. On Monterey's WKWebView 15.6 the focus ring and accent surfaces resolve invalid, and a bundled dependency ships a RegExp lookbehind that is a PARSE-time SyntaxError no polyfill can reach. Option B — actually supporting 15.6 — means setting build.target back, replacing 64 color-mix() calls, dropping Tailwind v4 and replacing that dependency, indefinitely, for an OS that stopped receiving security updates in late 2024. The council was unanimous on A, and the precedent is uniform (Chrome 117, Electron 27, VS Code, Firefox 116). minimumSystemVersion is also the guard: macOS itself refuses to launch a bundle below it, so a Monterey user gets an explicit OS refusal rather than an app that opens to a blank window — which matters because the Tauri updater has no per-OS gating of its own. Docs updated in the same change (README support table, docs/install/macos.md) and the webCompat floor assertion re-derived to 16.4, so the post-floor API list must be revisited the next time the floor moves. Closes #1268 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(macos): raise the floor in the macOS overlay too, and assert it Greptile P1, and correct: Tauri merges tauri.macos.conf.json OVER the base config for a macOS build, and that file carried its own minimumSystemVersion: 12.0. Changing the base config alone decided nothing — the shipped bundle would have stayed Monterey-installable while the base config, the README and the install docs all said 13.3. Worse, the guard I added read only the base config, so it would have gone on passing. A test that validates the wrong file is not a guard; it now asserts both, with a comment saying why the overlay is the one that ships. Also per review: the changelog entry was an editorial paragraph rather than a one-line entry, and the section was missing ### Docs. Both fixed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(webcompat): the module header still described the old 12.0 floor The floor moved to 13.3/Safari 16.4 in this PR and the test was re-derived, but webCompat.js still told the next reader the oldest supported WebView was 15.6 — which would make every fill here look mandatory instead of retained for Linux's unpinnable WebKitGTK (CodeRabbit). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
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. |
||
|
|
9736fd4859 |
release: v0.4.1 (#1239)
* release: v0.4.1 Seven user-reported issues fixed since v0.4.0 (#1221–#1229). Version bumped across the single source of truth (frontend/package.json) and its three toolchain mirrors; [Unreleased] renamed to the release section that release.yml extracts verbatim as the GitHub Release body. Docker tag examples in docs/install/docker.md and deploy/dockerhub-overview.md updated to 0.4.1 (docs-sync rule). * release: #1239 review — sync Cargo.lock to 0.4.1 Greptile: the manifest said 0.4.1 while Cargo.lock still recorded 0.4.0, so a `cargo build --locked` (and the Tauri bundler's own locked build) would fail on the mismatch. Regenerating locally updated it but it was never staged. * release: re-sync [0.4.1] after the fix merges, date it 2026-07-27 Picks up everything merged since the section was first written: the first-run wizard chrome (#1241), the MCP host allowlist (#1249), the macOS 12 startup crash (#1245), and the six error-message fixes (#1247, #1251, #1254, #1256, #1257, #1262). Deliberately NOT included: - the dub delete-resurrection fix (#1252, #1253) — split to #1270 after it needed six rounds of correction, the last two finding that the fix did not close the reported case and that its own bound reintroduced it; - the Linux AppImage WebKit fix (#1258, #1244) — held on #1265 pending confirmation on a Mesa 26.1 host, which nobody has run. |
||
|
|
d800f77e26 |
fix(rocm): #1228 review — a remap is only a fix if the build ships the target
Two P1s, both correct: - Being IN the override map was treated as proof of compatibility. If the wheel ships neither the native arch nor the remap target, setting HSA_OVERRIDE_GFX_VERSION only changes WHICH kernel is missing — gfx1151 with a gfx1030-only build was routed to the GPU and would fail at launch. Both arch_unsupported() and _configure_rocm_if_needed() now require the target to be present, and fall back to CPU otherwise. - An EMPTY arch list means the build's metadata is unavailable, not that the GPU is unsupported. The remap branch read that unknown state as a confirmed mismatch and would push a natively-supported gfx1151 onto foreign gfx1100 kernels. Now fails open and changes nothing, matching the fail-open contract the rest of the probe follows. ROCM_GFX_OVERRIDES values are now the target gfx NAME rather than the HSA version string, so the "is the target present?" check is a direct membership test; hsa_override_for() derives the env-var form, covered by a test that every entry in the map converts cleanly. Also: CHANGELOG entries shortened with refs last, and the MD028 blank line between the two docker.md blockquotes (CodeRabbit). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
791dae69db |
fix(rocm): stop force-routing every AMD GPU to the CPU (#1228)
The GPU compatibility gate built a CUDA-namespace tag from `get_device_capability()` (`sm_115` on a gfx1151 Strix Halo) and looked for it in `torch.cuda.get_arch_list()` — which on a ROCm wheel returns gfx *names* (`gfx1100`, `gfx1151`, …). The two namespaces can never intersect, so `check_device_compatibility()` returned False on every ROCm build and `get_best_device()` silently returned "cpu": torch saw the GPU, `torch.cuda.is_available()` was True, and the app ran on the CPU anyway. The comparison was copy-pasted in three places, all with the same bug, so it now lives once in `core.device_caps.arch_unsupported()` and branches on the build (gfx names on ROCm, sm_/compute_ tags on CUDA): - `model_manager.check_device_compatibility` — the CPU force-route, plus a ROCm-specific remedy instead of telling AMD users to install a cu128 wheel - `device_caps._probe` — the kernel-risk note that downgraded the routing badge - `engine_env._cuda_arch_supported_for_compile` — torch.compile off on all AMD `_configure_rocm_if_needed` also applied `HSA_OVERRIDE_GFX_VERSION` from a static map without checking whether the GPU needed it, remapping cards the installed build supports natively onto foreign kernels. It now applies only when the native GFX ID is genuinely absent from the arch list, and knows gfx1150/gfx1151 (Strix Point/Halo). Regression test: tests/test_rocm_arch_gate.py pins the reporter's host resolving to "cuda", a genuine ROCm mismatch still being caught, the CUDA path (#756 Blackwell fallback) unchanged, and the narrowed override. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f92e4223db |
docs(docker): refresh registry tag examples 0.3.22 → 0.4.0
Update the exact-version / minor / ROCm pin examples in the Docker Hub overview (deploy/dockerhub-overview.md — source of the hub.docker.com page, re-synced on this main push) and docs/install/docker.md to the v0.4.0 release. GHCR's package page inherits the repo README and the current org.opencontainers.image.description label, both already accurate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
433d4bc659 |
Merge remote-tracking branch 'origin/main' into fix/1177-surface-backend-diagnosis
# Conflicts: # CHANGELOG.md |
||
|
|
fc7fbf1227 |
fix(gpu-pool): bound execution, not queue wait (#1190, #1202)
A job queued behind a busy 1-worker pool burned its whole 300s budget without executing an instruction, then reported "too heavy for the available compute". The clock now starts when a worker picks the job up; queue wait has its own generous bound and surfaces as a retryable saturation error. Also: reset() no longer cancels innocent queued peers; the timeout message stops claiming capacity was restored (the abandoned job keeps the device until it drains); every GPU dispatch uses the shared length-scaled budget; watermark embeds move off the GPU pool; /v1/audio/speech gets 429/503 + Retry-After; a timed-out batch segment fails the job instead of shipping a silent gap. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
aab138ea57 |
fix(backend): surface the shell's start-failure diagnosis instead of "can't reach the backend" (#1177)
The reporter's string is apiFetch's LAST fallback, reached only when no crash
marker exists AND the shell's lifecycle stage is 'failed' or 'unknown'. The
'failed' half was the bug: `BootstrapStage::Failed { message }` carries the
whole diagnosis — exit code plus a ~30-line stderr tail, or the precise reason
`ensure_venv_ready` refused (Intel Mac, a failed `uv sync`, a blocked GitHub) —
and `backendLifecycleStage()` returned only the stage tag, throwing the message
away. Every backend-start failure mode collapsed into one generic, evidence-free
sentence that was also factually wrong: it is not starting, and it will not
recover on its own.
- backendLifecycleStage() returns `{ stage, message }`; a `failed` stage gets
its own branch in apiFetch that surfaces the shell's diagnosis, scrubbed.
- BackendStartFailureNotice renders it after the splash is gone, reusing the
splash's `detectHints` matcher (shared, so the two can't drift) and the
existing bug-report affordance. No Retry advice for unrecoverable failures.
- Rust retains the last `Failed { message }` past a later stage transition
(Retry sets Checking, the supervisor sets StartingBackend) so a respawn can't
erase the first diagnosis; exposed as `last_bootstrap_failure`.
- `bun desktop` prints the exit code and where to look instead of exiting
silently — the from-source twin of the same class ("builds but won't launch").
- Scrub primitives extracted to utils/scrub.js so the transport layer can scrub
without a bugReport -> client import cycle.
Non-Tauri deployments are untouched: there is no shell to fail this way, so the
stage stays 'unknown' and #1164's deployment-specific message still stands.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
83f943bead |
fix: bot-review harvest (16 findings) + deterministic style/locale CI + reviewer configs
Harvested and verified every CodeRabbit/Greptile finding from PRs #1175, #1189, #1192, #1195: 16 real ones fixed (fallback ASR preflight bypass, VRAM release on stream exit, typed 409 parity, uv env independence, path-privacy in errors, MCP clone_voice hardening, CaptureWidget WS guard, test hygiene), 4 refuted with evidence, rest documented as deliberate design or deferred. Deterministic CI replaces hand-enforcement: tests/test_changelog_style.py (quiet one-liner format) and tests/test_locale_parity.py (21-locale key/placeholder lockstep with a ratchet baseline) — the latter surfaced and fixes 151 already-broken locale strings. CodeRabbit/Greptile carry the house rules via .coderabbit.yaml + greptile.json; CLAUDE.md gains the harvest-before-merge and never-accept-as-is rules. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
933743e336 |
fix: resolve open issue batch — #1172 #1173 #1174 #1185 #1186 #1188
- validate managed binaries before exec; 0-byte GGUF placeholders fail actionably instead of "Exec format error" (#1172) - KittenTTS: tokenizer-measured chunking to the ONNX 512-token cap; clear 400 for unspeakable input (#1173) - clean SIGTERM during weight load: shutdown-aware loader, benign cancelled-load classification, lifespan hardening, scoped log silencers (transformers load + alembic fileConfig) (#1174) - broken ASR deep-imports (lightning_fabric) mark the engine unavailable with a repair hint and fall through (#1185) - uv cache + managed Python follow the chosen install drive on Windows (cherry-picked cross-drive class fix + spaces/D: tests) (#1186) - adaptive silence-removal ladder for quiet clone references; localized actionable error for truly silent clips, all 21 locales (#1188) - CHANGELOG: consolidated Unreleased into the quiet one-liner style Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
63fd497caf |
feat: TTS-only first run, platform-curated ASR, guided OS permissions, parakeet-mlx
Only the TTS model (~2.4 GB) is required on first run; ASR models are per-platform curated picks (curated_on in models.yaml) installed on demand. Every transcription surface returns a typed asr_model_missing error with a one-click download CTA instead of silently pulling multi-GB Whisper weights. Settings -> Models is a grouped, platform-aware catalog. New guided permissions UX (wizard System Check + Settings -> Permissions + mic pre-flight) with native mic-state checks and OS settings deep-links. New parakeet-mlx engine brings Parakeet TDT v3 to Apple Silicon (language-gated capture preference so multilingual dictation never regresses). Docs: expressive-speech page, Flush/Unload + CPU-fallback triage, clone-length FAQ. Hardening: preflight fails open for custom model pins, ROCm curation no longer inherits NVIDIA picks, Windows mic probe reads the NonPackaged consent key, CaptureWidget setup race fixed, offline-cache CI simulation fixes so empty-cache runners stay green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
08e19e0c1c |
feat(uninstall): opt-in, content-free app_uninstalled ping in the uninstall scripts
Before deleting anything, uninstall.sh / uninstall.ps1 now send ONE
best-effort `app_uninstalled` event — but only when the user opted in:
consent is read from the same prefs.json the app writes, AND the ping needs
the backend-written analytics_info.json (present only while analytics is
enabled — consent + build token — and removed on opt-out), because the
generic scripts ship no token of their own. Payload is content-free: app
version, OS name, random per-install id. 2-second timeout, silent failure,
one honest console line ('Sending anonymous uninstall ping (you opted in to
analytics).'); not opted in => nothing sent, nothing printed, and the
dry-run never sends either way.
Tested by exercising the bash script for real (fake $HOME + a curl shim
recording argv: consented/not-consented/dry-run/missing-info flows, plus
data-still-deleted-after-ping) and by static contract checks on the ps1
(consent gate, TimeoutSec 2, try/catch, no baked token). docs/install/
uninstall.md documents the behavior (docs-sync).
|
||
|
|
e6b1179001 |
feat(dev): loud backend exit banner for bun run dev + docs + changelog (#1164)
In dev there is no supervisor: concurrently's --kill-others-on-fail tears the whole stack down the moment uvicorn exits, the cause scrolls away with the terminal, and the browser tab just says it can't reach the backend — which is exactly how #1164 arrived with zero diagnostics. - scripts/dev-backend.mjs: dev:api now runs uvicorn through a wrapper (command args byte-identical, stdio inherited). On a non-Ctrl+C, non-zero exit it prints a boxed banner: exit code/signal, the last 20 lines of omnivoice.log (data dir resolved exactly like backend/core/config.py), an OOM hint (SIGKILL/137 + the Linux journalctl -k check), and a pointer to the crash notice the run sentinel raises on the next backend start. Exits with the child's own code so --kill-others-on-fail still works. Verified live: started the dev backend, SIGKILLed it, banner printed with the real log tail and exit code 137. - docs-sync: troubleshooting.md gains §14c (browser/dev/Docker crash forensics: the mode-aware error, the dev banner, run_sentinel.json / last_run_crash.json / GET /system/last-run-crash, cap+ack+version-gate semantics) and §14's crash-notice blockquote no longer implies the notice is desktop-only; CONTRIBUTING.md documents the dev:api wrapper. - CHANGELOG.md: [Unreleased] entry for the #1164 class fix. Tests: tests/frontend/devBackend.test.mjs (5) — the uvicorn args are pinned byte-identical, data-dir resolution mirrors config.py, tail/banner content incl. the OOM shapes. |
||
|
|
b17df5c7a3 |
docs(docker): refresh the Docker Hub overview and docker guide
- Add a what-you-need line (RAM/disk/GPU from the README requirements table, compressed pull sizes measured from the registry) so homelab users can size the deployment before pulling. - Update stale version examples (:0.3.6 / :0.3.17 -> :0.3.22). - Fix the 'main is always one patch ahead' claim — with AUTO_VERSION_BUMP off, main can equal the released version; say 'at or ahead of the last release', which is true in both modes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
9ecb810946 |
fix(shell): version-gate crash markers, pin the WebView repair contract, run the Rust suite in CI (#1145)
* fix(shell): version-gate crash markers, pin the WebView repair contract, run the Rust suite in CI Two deferred items from the recurrence audit, plus the CI gap that made them possible: - crash.rs: a persisted "backend crashed" marker now only surfaces for the release that wrote it. After an upgrade, markers from the previous version (quite possibly the build whose crash the upgrade fixed) are ignored and pruned on read instead of resurfacing unacknowledged as if the new build had crashed. backend_version gains #[serde(default)] so legacy version-less markers still deserialize — as "", which the gate treats as stale by design. Preview stamps (X.Y.Z-N) count as their release. - commands.rs: the #879 WebView2 cache repair's filesystem half is extracted into clear_webview_cache_at() (paths + retry policy as parameters, zero behavior change) and its contract is pinned by tests: no marker → nothing touched; marker consumed first, unconditionally (one-shot — a failing repair can never loop across launches); missing cache is success; a locked cache is retried then abandoned with a log, never bricking startup. - ci.yml: the Tauri shell check only ran `cargo check`, which neither compiles nor runs #[cfg(test)] code — so the shell's ~90 unit tests (crash.rs, reset.rs, bootstrap.rs, …) never executed anywhere in CI. `cargo test --lib` now runs them natively on all three OSes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(shell): crash-notice read path is strictly read-only — a prune-save there could destroy a fresh marker Greptile's P1 is real, and hotter than stated: get_last_backend_crash is not just a startup check — streamDropError (#1119) polls it every second for 8 s after a stream drops, which is exactly when the death watcher is inside record_crash's load→push→save. The previous commit's read path did load→prune→save when stale-version markers existed (the post-upgrade state), so a poll could load the pre-crash snapshot, lose the race, and save over the freshly recorded marker — silently deleting the only evidence of the crash it was being polled to find. Smallest fix: reads never write. The read path (extracted as read_notice_from(path, version) so the contract is testable) filters stale-version markers in memory only; disk pruning stays on the write paths (record_crash, acknowledge_backend_crash), where load-modify-save already existed pre-PR and is paced by a crash or a user click rather than a 1 Hz poll. Regression test pins the file as byte-identical across reads, stale markers filtered and current ones surfacing as before. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: mergetest <nizam4103@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
4e5d795832 |
fix(uninstall,storage): remove the saved-env leftover; count sidecar engines in disk usage (#1108)
Two recon findings from the reset work, fixed properly (whole class + tests +
docs), plus the destructive reset path is now exercised end-to-end.
1. ~/.config/omnivoice/env survived every uninstall. The app persists the
model-cache location (and a possible HF_TOKEN) there via
backend/core/user_env.py, but the in-app "Remove all data" (uninstall.rs),
uninstall.sh, and uninstall.ps1 all walked past it — so a reinstall silently
inherited the old file and redirected downloads to a maybe-deleted location.
All three now remove it. It's the same expanduser("~/.config/omnivoice/env")
path on every OS, so the Windows script uses %USERPROFILE%\.config\omnivoice.
is_recognizably_ours accepts it (contains "omnivoice"); docs tables updated.
2. Disk usage measured the wrong engines dir. storage_report.default_engines_dir()
returned backend/engines (built-in engine *modules*, no venvs), while sidecar
installs live in DATA_DIR/engines/<id>. So a multi-GB IndexTTS-2 install was
invisible in the engine-venv category and rolled into data/"other". Now points
at DATA_DIR/engines and sizes the WHOLE install (venv + checkout + weights),
with the data category claiming that subtree so it isn't double-counted.
Reset hardening: extracted purge_scopes() as a pure fs function (no AppHandle),
so the actual delete loop runs in tests against a real on-disk install tree —
"everything" wipes the install but spares the venv/foreign temp/sibling folders,
a settings reset keeps content+config+models, and a poisoned data_dir="$HOME"
deletes NOTHING. This is the live drive-through of the destructive path, minus
the GUI.
Also: gitignore the node_modules symlink form (the directory rule node_modules/
never matched a worktree symlink, so it kept slipping into commits).
Tests: Rust 78 (6 new), storage_report 20 (2 new incl. once-not-twice count +
default-dir guard), frontend 1207, i18n probe green, format+lint clean.
Co-authored-by: mergetest <nizam4103@gmail.com>
|
||
|
|
94093605eb |
feat(settings): factory reset gets scopes — preferences, settings, assets, everything (#1100)
* feat(settings): factory reset gets scopes — preferences, settings, assets, everything Factory reset did exactly one thing: clear localStorage. The only other option was "Remove all data", which deletes the Python env and quits. Between "forget my theme" and "wipe the machine" sat every reset a user actually needs — drop a corrupt model download, remove a wedged sidecar engine, put the settings back without losing a single voice — and none of them existed. Settings → Storage → "Reset & remove" now offers four tiers (UI preferences / all settings / downloaded assets & models / everything OmniVoice did) plus a per-scope checklist. Every scope shows its real on-disk size, and the number on the confirm button is exactly what gets freed. Why the shell and not the backend: a loaded model memory-maps its weights out of the HF cache (locked on Windows while mapped), and ensure_dirs() runs at import, so a backend cannot delete voices/ or outputs/ and still write to them. reset.rs stops the backend, deletes, and starts it again — and that restart is also the repair: the fresh process re-runs ensure_dirs() and alembic, so a removed database comes back empty rather than missing. retry_bootstrap's respawn path is extracted to bootstrap::respawn_backend so both callers share one implementation. Deliberate scope choices: - "Everything" stops short of the managed Python env, so a reset hands back a working app on the first-run screen. The env is the uninstaller's business. - A settings reset keeps the storage locations (config.json, the user env file). Clearing the model-cache pointer would strand gigabytes at a path the app no longer looks in — install shape is not a preference. - content deletes the DB with the media: rows without files is how you get a library full of broken entries. - The shared HF cache is flagged as shared only when it IS — computed, so Windows and portable installs (app-private cache) get no caveat they don't need. Safety: nothing is removed unless it sits inside a validated root — one carrying an OmniVoice-owned path component OR holding an OmniVoice signature file, which is what lets a custom data dir on an external volume be cleared while a mis-set data_dir: "/" is refused. Voices/projects/audio need the word typed. 9 Rust tests (guard, scope composition, shared-cache computation) + 14 frontend (planning purity, typed confirm, disk-vs-frontend split, shared warning). Border utilities follow the design guard (tests/test_no_literal_borders.py). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(settings): give the Storage panels a design — proportional sizes, live totals, real tokens "Remove all data" listed four folders as a flat run of text: a 7.5 GB model cache and a 391-byte config file rendered at identical visual weight, so the one thing worth seeing — where the space actually went — was the one thing you couldn't. And the 391 B folder said "0 KB", which reads as "nothing here". - New shared StorageTargetRow, used by BOTH destructive panels so they read as one system: icon, label, dimmed path (truncated, full text on hover), size, and a proportional bar showing that row's share of what will be freed. Unticked rows claim none of the bar — the bars must sum to what the button promises. - The shared HF cache moves OUT of the confirm dialog into its own "Optional" row with the checkbox and the caveat in the list. Ticking it now moves the running total in front of the user, instead of springing a different number on them at the point of no return. The dialog lists exactly what is going. - One byte formatter for both panels (settings/bytes.js). models/format.fmtBytes floors at kilobytes, hence "0 KB"; it stays where it is for the model store. Real fix underneath: three of the tokens these panels styled with DO NOT EXIST (--chrome-fg-subtle, --chrome-bg-raised, --color-warning). An undefined var() makes the declaration invalid, the browser drops it, and the element silently inherits — which is why the paths that were meant to recede rendered at full body weight. That is a whole class of bug that fails invisibly, so it gets a guard: src/test/cssTokens.test.js fails on any var(--token) in JSX not defined in a stylesheet, with runtime-injected tokens (Radix, inline-style hues) allowlisted by reason. Six pre-existing offenders elsewhere in the app are recorded as known-broken and ratcheted so the list can only shrink — they are real bugs, but each is a visual change that wants its own review. Frontend suite 1196 → 1205 (6 UninstallPanel component tests incl. the live total and the bar proportions; 3 token-guard tests, verified fail-before). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: green CI + finish the token sweep + snapshot the panels Three things on top of the redesign: 1. CI was red on tests/probe/test_probe_i18n.py — removing the eight dead `factory_reset_*` keys from en.json orphaned them in all 20 other locales (the probe forbids a non-en key absent from en). Removed them everywhere. This guard scans locales at pytest time; a frontend-only run never sees it. 2. Finished the undefined-token sweep instead of grandfathering it. Six bare `var(--token)` references resolved to nothing; the only genuinely undefined, fallback-less one in shipping panels was `--chrome-input-bg` (input fields AND progress-bar tracks AND skeletons across StoragePanel, StorageUsagePanel, HistoryRetentionPanel, ModelStoreTab — tracks were rendering with no background at all). Repointed to --chrome-hover-bg. The rest (--chrome-menu-bg, --chrome-bg-inset, --border, --input-bg, --muted) already carry `var(--x, fallback)`, which is valid CSS. So cssTokens.test.js now checks only the BARE form and ships with zero exceptions — no known-broken ratchet, because there is nothing left broken. 3. Registered both Storage panels in the visual-regression harness (a Tauri `invoke` stub added to providers.jsx alongside the existing fetch stub) and committed baselines across all three themes. This is how I actually looked at the redesign: the bars render proportional (the 720 KB voices row fills, the 391 B row is a sliver), the shared-cache row sits in its own Optional group, and every token now resolves in default/midnight/catppuccin. `_forceAdvanced` on ResetPanel opens the checklist for the snapshot; no effect on the toggle. Full backend suite 2897 passed (incl. the i18n probe). Frontend 1205. * style: oxfmt the new panels and specs Format-check is a CI gate (oxfmt --check); the new files weren't run through oxfmt --write. No behavior change. * chore: stop tracking the node_modules symlink A worktree-local symlink slipped past .gitignore (which lists node_modules/ — the directory form — so it never matched the symlink file). Removed from the index; the symlink stays on disk for local test runs. --------- Co-authored-by: mergetest <nizam4103@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0421be966e |
feat(settings): in-app uninstall — Settings → Storage → Remove all data (#1089) (#1099)
* feat(settings): in-app uninstall — Settings → Storage → "Remove all data" The v0.3.19 uninstaller was a SCRIPT, which never reaches the people who need it: anyone who installed the .dmg / .msi / AppImage has no repo to run scripts/uninstall.sh from — exactly the reporter in #1089, an AppImage user. "Where is uninstall in the app?" had no answer. Now it does. New Tauri commands (uninstall.rs): - uninstall_scan — every folder this install owns, with real sizes, resolved through the same setup.rs helpers the app itself uses, so custom + portable locations are cleaned instead of the defaults being assumed. - uninstall_purge — stops the backend (marking the kill intentional so the #567 supervisor doesn't respawn one into the directories being deleted), removes the folders, and lets the UI quit the app: the Python env it runs on is gone, so there is nothing to return to. This lives in the Rust shell, not the backend, because the biggest thing to remove is the managed Python environment and the backend is RUNNING FROM IT — a process can't delete its own interpreter (and Windows locks the files). Safety: every path must pass is_recognizably_ours() before any remove_dir_all — absolute, not `/` or $HOME, and carrying an OmniVoice-owned component (unit tested both ways). The shared Hugging Face cache is reported separately and is OPT-IN behind its own checkbox with the caveat spelled out: it's the standard HF cache other ML tools share, so sweeping it up silently would delete models this app never downloaded. Deleting voices/projects is irreversible, so the confirm requires TYPING the word, not just a click. Also fixes a real bug in what shipped in v0.3.19: the scripts and docs missed where the BACKEND writes its logs — ~/.local/state/OmniVoice on Linux and %LOCALAPPDATA%\OmniVoice\Logs on Windows (backend_log_path(), backend.rs) — so every Linux/Windows uninstall left a stray log dir behind. Covered now in the scripts, the docs, and the in-app scan. And the scripts now ship as release assets, so cleanup is possible without launching the app at all. Rust: 2 new guard tests. Frontend: 6 new tests (the size on the confirm button must equal what actually gets deleted); suite 1182 passed. Docs synced. Refs #1089 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(settings): drop token border utilities from UninstallPanel (design guard) tests/test_no_literal_borders.py::test_no_token_border_utilities_in_jsx is a backend guard that scans JSX — so a frontend-only test run misses it. It forbids `border-[var(--chrome-border)]` structural utilities: the app-wide border removal converted every panel/row frame away from them, and they render a stray hairline the moment the token doesn't resolve transparent. Row dividers → spacing + an alternating `--chrome-hover-bg` tint; the opt-in checkbox card → a background tint; the confirm input → the sanctioned arbitrary `[border:1px_solid_var(--chrome-border)]` property form the other settings inputs already use (explicitly not flagged by the guard). Guard green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: mergetest <nizam4103@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b1a7ddc374 |
feat(install): clean uninstaller + a straight answer to "where is my data?" (#1097)
A Linux AppImage user asked which folders to delete to remove OmniVoice and whether an uninstaller exists (#1089) — they had to guess. They shouldn't have to: the app is fully local, so uninstalling IS just deleting the folders it wrote, and we never documented them. - scripts/uninstall.sh (macOS/Linux) + scripts/uninstall.ps1 (Windows): find every OmniVoice folder — app data, the multi-GB managed Python env, config, logs — plus, listed SEPARATELY because it is a shared cache, the Hugging Face model cache. Print each with its size as a DRY RUN and stop; delete only on --yes (--models / -Models to include the shared cache). They honor the same env overrides the app reads (OMNIVOICE_DATA_DIR, OMNIVOICE_CACHE_DIR, HF_HOME, HF_HUB_CACHE), and never touch the app binary or anything outside the paths they list. - docs/install/uninstall.md: the complete per-platform path table (what each folder holds and how big it is), the shared-HF-cache caveat, custom/portable locations, per-platform steps to remove the app itself, and what to keep if you plan to reinstall. - Linked from the README FAQ, SUPPORT.md, and install troubleshooting. Paths mirror backend/core/config.py + frontend/src-tauri/src/setup.rs. Verified on macOS: dry-run lists the real dirs; sandboxed HOME runs confirm --yes removes app folders while KEEPING the shared cache, --models removes it, and the env overrides retarget correctly. Closes #1089 Co-authored-by: mergetest <nizam4103@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
23367cccaf |
fix: first-run wizard version + mirror-unreachable rescue + lifecycle-aware backend reachability (#1094)
Three fixes from the same first-run session report:
- SetupWizard shows v{APP_VERSION} in its masthead (same identity mark as
the install splash footer), so setup screenshots identify the build.
- A dead configured HF mirror no longer strands the wizard: the
install_error SSE now carries docs_topic (core.failure.classify), and
WizardLibrary renders the MirrorRescue quick-pick (extracted from
SetupWizard, now including the official preset) next to the failed row,
retrying it the moment a new endpoint is applied. PUT /hf-mirror clears
the install cooldowns (no 429 on the immediate retry) and clearing to
official also drops the legacy hf_endpoint pref that silently kept the
dead mirror in effect. The hint's false "applied when the app starts"
claim is corrected: downloads resolve the endpoint per call, retry
first, restart only if it still fails.
- "Can't reach the local OmniVoice backend" stops firing during real
start/restart windows: a respawn takes 10-20+s (venv spawn + torch
import) but the transport cascade gave up at ~2.9s. apiFetch now asks
the shell (bootstrap_status via utils/backendLifecycle) whether a
start/restart is in progress and keeps retrying while it is (capped at
120s, matching the supervisor's respawn budget); the new
BackendRestartBanner finally implements the reconnecting banner the
#567 supervisor has emitted events for all along. Truly dead backends
(or non-Tauri deploys) still error promptly.
Regression tests for all three layers; docs synced
(downloading-models.md, troubleshooting.md §14b); CHANGELOG [Unreleased].
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
9c81e3389d |
feat(network): automatic Hugging Face endpoint selection — probe, pick, remember (#1082)
* feat(network): automatic Hugging Face endpoint selection — probe, pick, remember Restricted-network first-runs (the #984 class: huggingface.co unreachable, user dead-ends before discovering the mirror setting) now self-heal by default, while explicit endpoint choices are never second-guessed. - New backend/services/endpoint_race.py: parallel HTTPS reachability + latency probes of huggingface.co and the hf-mirror.com community mirror (3s timeouts). Probes are the only signal — no geo-IP, no third-party calls. Reachable beats unreachable; with both reachable the official endpoint wins unless the mirror is decisively faster (anti-flap hysteresis). The pick is cached in prefs and re-raced only on first run, a network-classified download failure, staleness (>7 days), or an explicit "Test again". - Manual mode is sacred: HF_ENDPOINT env, an hf_endpoint pref, or any explicit Settings pick disables auto-switching entirely; OMNIVOICE_HF_ENDPOINT_MODE=manual is a hard opt-out. - Wiring: the wizard preflight races endpoints when nothing is configured (honest copy when the mirror wins; warn-not-block when nothing is reachable); Model Store installs and the model-cache auto-repair resolve their per-call endpoint= through the cached decision, and a network-classified failure re-races once per repo per process and retries on the new winner (same guard pattern as the cache-recovery ladder). - Settings → Models → Hugging Face mirror gains "Auto (recommended)": shows the current pick, measured latency, last-checked time, and a "Test again" button (POST /api/settings/hf-mirror/test). Existing explicit configs surface as the matching manual mode. Panel notes that hf_hub checksums every download regardless of endpoint. - Tests: policy/cache/failover matrices in tests/test_endpoint_race.py, preflight + settings + repair-failover integration with mocked probers, HFMirrorPanel mode tests, and a suite-wide conftest guard that pins the probers so no test can hit the real network. - Docs: downloading-models.md and install/troubleshooting.md describe the automatic default and both opt-outs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(changelog): Unreleased entry for automatic HF endpoint selection Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(tests): endpoint-probe pin uses an isolated MonkeyPatch and clears the decision cache; dtype guard tolerates stubbed torch The autouse probe pin requested the shared monkeypatch fixture, hoisting its setup earlier for every test and reordering teardown against the fp16 guard — which then ran torch.get_default_dtype() on test_torch_compile_gate's SimpleNamespace stub. The pin now uses its own MonkeyPatch context and also clears the prefs-cached endpoint decision per test (one test's auto pick leaked into other tests' preflight labels on CI ordering). The dtype guard additionally skips non-module torch stubs outright. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(tests): endpoint env vars can no longer leak out of the mirror-settings suite set_hf_mirror writes os.environ[HF_ENDPOINT] during the test, and monkeypatch.delenv(raising=False) on an absent var records nothing to undo — so the write leaked process-wide and flipped later suites' preflight network checks into the explicit-endpoint branch (the CI-order failures). Guaranteed save/restore autouse fixture at the source, plus defensive env shedding in the preflight suite. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: mergetest <test@local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
254f071b45 |
fix(docker): ship alembic.ini in the image, add an image-level HEALTHCHECK (#1080)
Migrations in Docker fell back to the additive-column self-heal because alembic.ini was never copied; the real migration chain now runs. The HEALTHCHECK covers plain docker-run (compose files keep their own), with a start period sized for first-boot schema creation. Docs example tag freshened. Co-authored-by: mergetest <test@local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
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>
|
||
|
|
1808a373a1 |
fix(linux): AppRun workaround detection reads the BUNDLED WebKitGTK version, not the host's (#961 follow-up) (#1024)
The launcher decided whether to export WEBKIT_DISABLE_COMPOSITING_MODE by asking the host's pkg-config — but LD_LIBRARY_PATH makes the BUNDLED libwebkit2gtk the one that actually runs, so on any machine where the two diverge the detection read the wrong number. This was the second bug identified during #961's investigation (the reporter built from source, so their dev packages answered pkg-config with a healthy version while the shipped bundle ran an older lib) and was explicitly deferred in #1007 as not-safely-fixable at runtime. The fix makes it knowable by construction instead: inject-apprun.sh runs at bundle time ON the build host whose libwebkit2gtk gets bundled, so it stamps that version into .bundled-webkitgtk-version inside the AppDir. AppRun reads the stamp first and only falls back to host pkg-config for bundles predating it. Empty/unreadable stamp fails safe (workaround on), same philosophy as the missing-pkg-config path. Tests: 3 new cases in AppRun.test.sh — marker-beats-host in both directions (broken-marker/healthy-host and the #961 inversion, healthy-marker/broken-host) plus empty-marker fail-safe. Also wires AppRun.test.sh into pytest (tests/test_apprun_launcher.py) — it was previously run by NO CI job, so the launcher could regress silently. Also documents Windows install-to-another-drive behavior in docs/install/windows.md (#938): local drives work via the wizard's directory picker, mapped network drives are a Windows Installer limitation, and the data directory moves independently of the app. Co-authored-by: mergetest <test@local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
da4bef8e42 |
docs: correct troubleshooting §16 — mic bug was a missing entitlement, not an upstream limitation; changelog for #1016/#1020/#1021 (#1022)
troubleshooting.md §16 claimed the macOS microphone-permission bug was an unresolved upstream Tauri/wry limitation with no available fix. That was wrong: @MahdiHedhli read the wry/tauri sources more carefully and found the real cause — Tauri's Hardened Runtime default blocks mic hardware access without com.apple.security.device.audio-input in the bundle's entitlements, which also explains why TCC never listed the app. Their fix (#1016) is merged; §16 now documents the real mechanism, credits the correction, and keeps the record-elsewhere workaround for users on ≤0.3.12 builds. Also brings CHANGELOG [Unreleased] current for the three merges that lacked entries: #1016 (mic fix), #1020 (shutdown wait 3s→20s), #1021 (CI flaky-trio root cause + guard). Co-authored-by: mergetest <test@local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
7f8a42ce51 |
fix(tts): mlx-audio CSM cloning drops ref_text, breaking every clone attempt (#1012, #1013) (#1017)
MLXAudioBackend.generate() reads voice/ref_audio/language/speed from its kwargs but never extracted ref_text — it was built, then silently never passed through to self._model.generate(). CSM (sesame.py) only builds its cloning context when BOTH ref_audio AND ref_text are present; with ref_text missing, the context list stays empty and indexing into it raises "IndexError: list index out of range" deep inside mlx-audio, instead of the clone ever being attempted. Voice cloning on the CSM engine could never have worked as shipped. generation.py already threads ref_text all the way through — even auto-transcribing it via the GPU pool when the caller supplies ref_audio without one (~line 780) — so the value was always available in kwargs; it just never survived the crossing into this specific backend. Reported with the precise root cause and a working fix (community member independently diagnosed and patched it locally, confirmed working on MPS/0.3.12). Two-line fix: extract ref_text and pass it through when both ref_audio and ref_text are present (guards against passing an orphaned ref_text with no accompanying audio to engines that don't expect it). Tests: tests/test_engines.py — ref_text is passed through when paired with ref_audio, omitted when ref_audio is absent. Also documents the second bug from the same report (#1013): macOS microphone permission never prompts, so OmniVoice never appears in System Settings to grant access. Root-caused to an unresolved upstream Tauri/WebKit limitation (WKWebView's requestMediaCapturePermissionFor delegate — wry#1195, tauri#11951, fix wry#1196 still open/unmerged, no released version to bump to) — not something fixable here without an unverified native Rust/WKWebView hack this session has no way to test. Documented in docs/install/troubleshooting.md with the confirmed workaround (record elsewhere, upload the file). Co-authored-by: mergetest <test@local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
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> |