d91beef0fd314250d8d9b94de86dfea019a8bd96
8
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
04410a458d |
Release VoiceStudio 5.0.0 (#1487)
Complete the VoiceStudio identity, release documentation, assets, version mirrors, and safe cross-platform development startup. |
||
|
|
5be4a26903 | fix(appimage): ship the compatibility launcher | ||
|
|
93025d9a81 |
fix(dictation): stop the widget stranding an empty square, repair the swept data dirs (#1398)
The dictation hotkey could leave a blank dark square stuck on the desktop with no way to dismiss it. Three defects compounded: the tray listener's effect depended on [state], so it detached across an await on every state change and a press landing in that gap was lost; an idle pill renders null, so the window Rust had already shown was empty; and the opaque chrome background made that empty window a hard-edged square. Nothing could hide it — dismiss() is only reachable from the X button, Esc, or a post-session timer, none of which exist for a session that never started. Fixed at the invariant rather than the call sites: the listener subscribes once for the component's lifetime, the widget window's chrome background is transparent, and an idle-but-visible window reconciles itself to hidden. The reconcile is polled (a dropped press changes no React state, so there is nothing to key an effect off) and aborts if its effect is torn down mid-check, so it can never hide a dictation that has just started. Also in scope: - The rename sweep had repointed three data-dir literals at a brand-named directory that does not exist, so smoke-test.sh verified a directory the backend never writes and desktop-prod.sh silently stopped clearing backend state on Windows. Both invisible on macOS, where they are usually run. A guard test now pins the assignments specifically. - The dictation model picker's download sizes were wrong for all seven models, in both directions — Parakeet TDT v3 (the recommended default) understated 180 MB against an actual 670 MB, while the low-RAM fallbacks were overstated threefold, discouraging exactly the choice that would have helped. Measured from the published repos and pinned by a test. - The 0.6B Parakeet models now decode on more threads, capped by host cores and still overridable. - uninstall.ps1 gained a UTF-8 BOM (Windows PowerShell 5.1 mis-decodes its non-ASCII output without one), and sponsor.yml lost its last OmniVoice references. |
||
|
|
95de0afb02 |
fix(appimage): the preload probe never ran, and tested the wrong value (#1333) (#1356)
* fix(appimage): the preload probe never ran, and tested the wrong value Two CodeRabbit Majors on #1354, landed after merge because I merged before reading them. Both silently DISABLED the feature rather than breaking loudly, which is the shape that survives a green suite. 1. `command -v true` answers with the shell BUILTIN — the bare word "true", not a path — so `[ -x "true" ]` was false on every host, the probe always failed, the preload never happened, and #1333 was left exactly as it was. A builtin never involves the dynamic loader, so it could not have tested anything even if it had run. Now resolves a real binary (/usr/bin/true, /bin/true, or /bin/sh -c : as the guaranteed last resort). 2. The probe took our library alone, but the exported value appends any inherited LD_PRELOAD — so the probe could pass while the environment the app actually gets fails. It now probes the final value. The suite missed both because every existing case overrides the probe via OMNIVOICE_APPRUN_PRELOAD_PROBE. The new default-probe case is what closes that hole; the inherited-entry case is the discriminator for (2). Both fail against the previous AppRun. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(appimage): drop a probe fallback that could never resolve; cover /bin/sh CodeRabbit, valid. The PATH entry looked up `coreutils`, which is not an executable name, so that branch could never resolve — a fallback in shape only. Deleted rather than repaired: a PATH lookup is what caused the original builtin bug, and the list already terminates at /bin/sh, which is present on any host that can run this script. That left the real last resort untested, which is how the branch above it shipped broken in the first place. `sh` needs `-c :` where `true` needs no argument, and with no argument `sh` reads stdin and hangs — so the new case points the probe override at the real /bin/sh and fails loudly if that branch is wrong (verified: breaking the argument turns it red). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
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. |
||
|
|
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> |
||
|
|
c32041289d |
Phase 1 Wave 3: AppImage launcher + .deb ffprobe + Docker LAN + Gatekeeper probe (closes #54, #56, #76, #80) (#93)
* fix(appimage): conditional WEBKIT_DISABLE_COMPOSITING_MODE launcher (#56) WebKitGTK 2.44.x and 2.46.x have a compositing-path regression on Wayland that blanks the AppImage's first paint on Fedora 44 / Ubuntu 24.04. Setting WEBKIT_DISABLE_COMPOSITING_MODE=1 forces the software fallback that works, but blindly setting it on healthy WebKit versions (2.48+) regresses those. This wave adds a conditional AppRun launcher that detects the WebKit version via pkg-config and only sets the env var on the broken ranges (plus a fail-safe when pkg-config is absent or the version is unknown). The launcher is injected into Tauri's AppImage staging dir via a beforeBundleCommand hook — see .planning/decisions/apprun-strategy.md for the spike outcome and rationale (Strategy B chosen). Phase 1 Wave 3 — Plan 01-03 Task 1. Closes #56 frontend half. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(deb): relocate bundled ffprobe out of /usr/bin to avoid conflicts (#76) Prior versions placed the bundled ffprobe at /usr/bin/ffprobe via Tauri's externalBin, which overwrites the system ffprobe on Ubuntu 26.04 and collides with apt-installed media-package ffprobe. Relocate the .deb-bundled ffprobe to /usr/lib/omnivoice-studio/bin/ffprobe via bundle.linux.deb.files, plus defensive maintainer scripts: - preinst: ensure target dir exists for upgrade flows - postinst: remove legacy /usr/bin/ffprobe ONLY when dpkg confirms our package owns it (never touches a user's distro ffprobe) - postrm: clean up the relocated path tree on purge/remove Rust side (tools.rs::resolve_ffprobe) now probes the new path on Linux, and backend spawn (backend.rs) carries both FFPROBE_PATH (legacy alias) and OMNIVOICE_FFPROBE_PATH (canonical) into the backend env. Python side (ffmpeg_utils.resolve_ffprobe) reads OMNIVOICE_FFPROBE_PATH first, falls back to FFPROBE_PATH, then to shutil.which("ffprobe"). 6 new unit tests cover the env-cascade resolution. Phase 1 Wave 3 — Plan 01-03 Task 2. Closes #76. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(frontend): centralised apiBase resolver for Docker LAN access (#80) Docker / LAN browser users hit the preview API at the LAN host's IP, not their local machine — the prior frontend/src/utils/media.js:20 hardcoded http://localhost:3900, which from a LAN client resolved to the client machine itself. Centralise via frontend/src/utils/apiBase.ts: 1. VITE_OMNIVOICE_API override (Docker compose / dev) always wins. 2. Tauri webview → http://localhost:3900 (unchanged behaviour). 3. Plain browser → ${window.location.protocol}//${window.location.hostname}:3900 (follows the page's origin — closes #80). 4. SSR / no-window → http://localhost:3900 (safe fallback). Grep-sweep confirmed media.js:20 was the only hardcode site (Assumption A4 in 01-RESEARCH.md verified). 6 new vitest cases cover the resolver. Phase 1 Wave 3 — Plan 01-03 Task 3. Closes #80 frontend half. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(backend): macOS Gatekeeper quarantine probe + INST-01 guard (#54) Adds backend/core/gatekeeper_detect.py which walks up from sys.executable to find the .app bundle and runs `xattr -l` to check for the quarantine extended attribute (com.apple.quarantine). On detection, the lifespan startup probe logs a structured warning and emits a system_error event through the existing event bus with error_class="GATEKEEPER_QUARANTINE", which Wave 2's React ErrorBoundary turns into a docs deeplink. Detection is informational only — we never auto-run `xattr -cr` (the app itself is quarantined and cannot fix its own state per Anti-Pattern in 01-RESEARCH.md). Users get a clear pointer to the workaround docs. GET /system/quarantine-status exposes the structured payload so the frontend can poll on first load. INST-01 (setuptools>=75.0 pin from PR #62) gains a PR-time guard in tests/backend/test_pyproject.py + a user-observable smoke check in scripts/smoke-test.sh (pkg_resources + whisperx import). 7 gatekeeper tests + 1 pyproject test added — all pass. Phase 1 Wave 3 — Plan 01-03 Task 4. Closes #54 backend half (Wave 2 owns the docs page + ErrorBoundary deeplink wiring). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |