* chore: bump to 0.5.0
Owner-requested minor bump. package.json is the source of truth; the
three mirrors (Cargo.toml, pyproject.toml, _FALLBACK_VERSION), the three
lockfiles and the branding pin move in lockstep, and the accumulated
Unreleased section becomes the curated 0.5.0 release notes — quiet
Highlights first, one-liner subsections after, duplicates folded.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: fresh README for the VoiceStudio era; 0.5.0 notes wear the release
README: 551 lines from 656 — a What's-new-in-0.5.0 section with real
captures, the engine tables corrected to the actual 16 TTS registrations,
a stale Settings path and a broken Colab link fixed, roadmap/FAQ/credits
trimmed to what earns its place.
Release notes: the quick-switch GIF and catalogue/gallery screenshots,
captured from the running app during the pre-bump test pass, embedded
after the Highlights; #1542's gallery work and the ffmpeg CI fallback
recorded in their subsections.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: the feature inventory's Remote Model Downloads mention survives the README trim
check-docs-drift requires every docs/features.yaml name verbatim in the
README; the overhaul folded the phrase away. It now lives in the remote
workers feature line.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: one blockquote, and an architecture claim that survives the opt-ins
CodeRabbit on #1540: MD028 blank line inside adjacent blockquotes, and
'every layer is on your machine' contradicted the opt-in remote paths
documented two sections away — it now states local-by-default with the
opt-ins named.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Three conflicts, all additive on both sides — resolved by keeping both
rather than choosing, since either side's entries were real shipped work:
* CHANGELOG.md — remote-GPU entries against branding, IndexTTS 2.5 and
the recording-input work
* setup/download.py — the per-target progress reset against main's
active-install tracking; both belong in the same finally block
* docs/features.yaml — the remote-worker and model docs against
docs/branding.md
Backend 5349 passed, frontend 1871 passed. `bun install --frozen-lockfile`
reports no changes, so the Docker build sees the same tree CI does.
Send individual jobs to GPUs on your other machines while everything else
stays local. Opt-in, off by default: with the toggle off there is no
listening socket, no certificate and no background loop.
Design follows remote/goal_v2.md, the council-revised goal doc. The
decisions that shaped the code, and why:
* A disconnect is an unknown outcome, not a failure. The original design
reassigned on disconnect while also describing the case where the worker
had already finished — following both guarantees duplicate execution. An
attempt now holds a grace window; a worker returning inside it commits
its result and no second attempt is ever made.
* At-least-once execution, exactly-once result commit. The result is
persisted BEFORE it is acknowledged, so a crash between the two cannot
silently lose a finished render.
* Deadlines are phased (accept -> model load -> execute -> deliver) and
liveness is a progress lease. The old fixed 30s execution budget was two
orders of magnitude below what this product actually does; silence is
the failure signal, not slowness.
* Capacity is derived from free VRAM, never configured: a static value
corrupts output under torch.compile thread affinity (#315) and aborts
the process on small cards (#567).
* A circuit breaker replaces the reliability-score/quarantine machinery,
which had no recovery path (no probation workload exists in a TTS
product) and penalised consumer networks for existing.
* Identity is a keypair the worker generates and never sends. A
server-assigned id is a name, not an authenticator, so revocation of one
would be theatre. Enrollment tokens are single-use and carry the control
plane's certificate fingerprint for pin-on-first-use.
Adds the domain core, scheduler, durable task store, gRPC transport,
worker agent, management API, Settings panel, and docs. Protobuf reserves
the tenant/trace/usage fields a hosted control plane would need, since
adding them later means upgrading a whole fleet.
Includes tests for the failure paths that matter: duplicate delivery,
stale-session fencing, reconnect reconciliation, grace expiry, breaker
attribution, and a real end-to-end TLS round trip.
Two bugs from the v0.4.2 rename sweep:
- The lazy model import was rewritten to `from omnivoice.models.omnivoice import VoiceStudio`, a class the library does not export. ImportError is not ModuleNotFoundError, so the #564 source fallback never caught it and /generate 500'd on every default-engine request. The class keeps its library name — it is a checkpoint-referenced identifier, not branding.
- alembic resolved a bare relative script_location against the process cwd, and the desktop shell launches the backend from frontend/src-tauri, so a pending migration killed startup. script_location and prepend_sys_path are now anchored with %(here)s, with path_separator = os so Windows drive letters and paths with spaces survive. alembic floor raised to >=1.16.
Regression tests verified fail-before/pass-after for both.
* fix(colab): pin torchvision==0.23.0 to resolve torchvision::nms operator missing error (#1357)
This resolves the runtime import failure of transformers.HiggsAudioV2TokenizerModel due to torchvision mismatch with torch 2.8.0. Changes:
- pyproject.toml: Added torchvision>=0.19 to dependencies, pinned to 0.23.0 in constraint-dependencies, and configured the pytorch-cuda source index.
- uv.lock: Regenerated lockfile to resolve torchvision 0.23.0+cu128.
- bootstrap.rs: Added torchvision to rocm_torch_reinstall_args and updated the matching unit test.
* fix(bootstrap): pin versions in ROCm reinstall path and add CHANGELOG entry (#1358)
* test(rocm): pin bootstrap.rs to pyproject constraint, mechanically
The PR adds a "Keep in sync with [tool.uv.constraint-dependencies]" comment
above rocm_torch_reinstall_args. That is the right instruction and a
comment cannot enforce it, so it becomes a test — CLAUDE.md`s convention is
that a rule a reviewer has to remember belongs in one.
It matters more here than the usual lockstep case: an AMD user`s install
does not come from uv.lock at all. bootstrap.rs shells out to pip against
the ROCm index, so whatever it names there is the Torch stack that user
actually runs, and a drift produces no install-time error — it surfaces
later as "operator torchvision::nms does not exist" or a silent CPU
fallback, which is #972 and #1357 from two different directions.
Three cases: the constraint block still pins the trio (so the rest cannot
pass vacuously), the ROCm pins equal it, and all three are reinstalled
together — a subset leaves the others on CUDA wheels the ROCm build cannot
pair with. Both drift cases fail against the previous, unpinned form.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: debpalash <4178343+debpalash@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Version files (package.json + the three mirrors) and both lockfiles bumped
0.4.1 → 0.4.2; [Unreleased] renamed to [0.4.2] — 2026-07-28.
Ships five user-facing fixes: the update toast, the restart guard that used
to discard in-flight work, the shutdown-is-not-a-crash 503, the self-healing
half-downloaded model, and the dub-history purge ordering — plus the
"Dismiss" mistranslation in five locales.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* 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.
Bump the single source of truth (frontend/package.json) and the three
toolchain mirrors (Cargo.toml, pyproject.toml, backend/core/version.py) plus
the lockfiles (Cargo.lock, uv.lock) from 0.3.22 → 0.4.0, and rename the
CHANGELOG's [Unreleased] section to [0.4.0] with milestone highlights.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
The dubbing release. package.json (source of truth) + the three mirrors
(Cargo.toml, pyproject.toml, version.py fallback) to 0.3.22; uv.lock +
Cargo.lock refreshed; CHANGELOG's Unreleased section (29 entries) becomes
## [0.3.22] — 2026-07-14 with the headline, split Added blocks merged.
Gates on the frozen content, all green before any version mutation:
backend 3033 + 204, frontend 1253, format, lockstep 6/6.
Co-authored-by: mergetest <nizam4103@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(engines): bundle en_core_web_sm — no mid-generation GitHub download
Post-merge review finding on #1144 (valid): with pip present, misaki/spaCy's
first-use auto-download WORKS now — meaning the first English MLX-Audio
generation performs a raw GitHub release download that (a) bypasses the app's
entire HF-mirror/endpoint system (restricted-network users have no recourse
mid-generation) and (b) fails offline. Local-first says default features
shouldn't spring surprise outbound requests at generation time.
en_core_web_sm-3.8.0 is now a pinned URL dependency in pyproject/uv.lock
(~12 MB wheel): it arrives at install/update time via the normal dependency
flow (where network failures are visible and retried), survives drift-sync
by construction, and spacy.util.is_package() finds it so misaki never
triggers its downloader at all. The #1143 containment stays as the backstop
for any other CLI-shaped dependency.
Also clarifies the venv test per review: pytest's interpreter IS the
uv-synced venv in CI and the packaged app, so find_spec verifies the lock;
the test now also pins the bundled model.
Validated: uv sync --frozen clean; en_core_web_sm importable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(engines): direct-URL dependency + frozen-bundle collection (review)
Two of three review P1s were real:
- Docker build break: `uv add` wrote a bare "en-core-web-sm" dependency with
the URL only in [tool.uv.sources] — Docker's `uv pip install --system .`
reads project metadata only, would resolve the bare name against PyPI
(where spaCy models don't exist), and the image build fails. Now a direct
"name @ url" dependency, the same form kittentts has always used, so every
installer (uv sync, pip, Docker) sees the same source. Re-locked;
uv sync --frozen clean.
- Frozen bundle: backend.spec ships mlx_audio, whose Kokoro path loads
en_core_web_sm DYNAMICALLY (spacy.load by name) — PyInstaller never sees
the import, so a frozen build would hit misaki's downloader at first
English generation. collect_all('en_core_web_sm') added inside the
mac-ARM block (plain data package, no nanobind hazard — the reason
collect_all is banned for mlx itself doesn't apply).
Declined with precedent: "hard-coded GitHub URL breaks restricted networks"
— kittentts has shipped as exactly this GitHub-release URL form in the same
dependency list since it was added; install-time GitHub fetches are the
project's accepted pattern (the bootstrap's gh-proxy mirror exists for
restricted networks), unlike mid-generation fetches, which this PR removes.
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>
The containment fix (#1143) makes a CLI-shaped dependency's sys.exit
survivable; this removes the reason it fired at all. mlx-audio's Kokoro
phonemizer (misaki) auto-downloads en_core_web_sm via spacy.cli.download,
which shells out to `python -m pip install <url>` — and uv-managed venvs
ship no pip, so the download always failed.
Why a real dependency instead of installing pip (or the model) ad-hoc at
engine load: the updater's drift sync reconciles the venv against the
lockfile (#1029/#1030, --inexact), so anything outside the lock is stripped
on the next update — the failure would quietly return after every release.
pip in pyproject/uv.lock survives sync by construction.
Validated against all lock consumers: uv sync --frozen clean; Docker's
`uv pip install --system .` reads pyproject; version-lockstep test reads
only the version field. Regression test asserts pip is importable in the
managed env.
Co-authored-by: mergetest <nizam4103@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The rejected PR #1110 had a genuinely careful PII-free event design, but shipped
three things a local-first app can't: exception autocapture ON (raw tracebacks —
home paths, and in this codebase HF tokens out of exception messages — bypassing
core.failure.sanitize() entirely), no user consent or disclosure, and 3,069 lines
of PostHog wizard scaffolding. This is the same capability with those fixed.
core/analytics.py, three rules, each enforced and tested rather than promised:
1. OFF unless the user says yes. TWO gates must both be true: a build-provided
POSTHOG_PROJECT_TOKEN *and* the user's analytics_enabled pref, default False.
A default install transmits nothing, so "nothing leaves your machine" stays
literally true for everyone who doesn't opt in. A broken prefs file fails
CLOSED. OMNIVOICE_ANALYTICS_DISABLED=1 is a hard kill switch above both.
Withdrawing consent tears the client down immediately — no restart.
2. NO exception autocapture. Explicitly disabled; a test asserts the constructor
arg, because the SDK's default is the leak.
3. Metadata ONLY, by allowlist. Every property passes sanitize_properties(),
which DROPS any key not on _ALLOWED_PROPS and refuses long strings — so no
future caller can leak a take's text, a path, or a voice name by adding a
field. text_length is the LENGTH; the text itself has no way through.
The person id is a random per-install UUID — not hardware, hostname, or username.
UI: Settings → Privacy → "Help improve OmniVoice" states in the panel exactly
what is sent, exactly what never is, and that it can be turned off — rather than
burying it in a policy. No destination in the build (any source build) → the
toggle isn't shown, because an inert switch would be a lie.
Docs: README FAQ answers "does OmniVoice collect any data about me?" honestly.
Also fixed a bug I'd introduced in my own wiring: the generation event referenced
variables not in scope, and the call site's bare `except: pass` swallowed the
NameError — so the event would have silently never fired. The call site now logs.
12 tests (default-off / opt-in without token still can't transmit / both gates /
kill switch / consent withdrawal / prefs failure fails closed / allowlist drops
text+paths+names / long strings refused / autocapture OFF / never raises /
random install id). Backend 2936 passed; frontend 1211 passed.
Refs #1110
Co-authored-by: mergetest <nizam4103@gmail.com>
package.json (single source of truth) + the three toolchain mirrors to 0.3.21;
Cargo.lock + uv.lock regenerated (version lines only). CHANGELOG [Unreleased]
renamed to [0.3.21] — 2026-07-12, "the memory release", sections merged into
house style (one Added, one Fixed).
Ships the 16 GB OOM class fixes end to end: idle-release the dictation ASR
(#1104) + one TTS engine resident at a time (#1105), plus the scoped
Settings → Storage reset/uninstall pair (#1089/#1099/#1100) and the release-
asset-split workflow fix (#1106) so this tag uploads to a single release.
test_app_version.py lockstep: 6 passed. bun install --frozen-lockfile: clean.
Co-authored-by: mergetest <nizam4103@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
package.json (single source of truth) + the three toolchain mirrors to 0.3.20;
Cargo.lock + uv.lock regenerated (version lines only). CHANGELOG [Unreleased]
renamed to [0.3.20] — 2026-07-12 with the release headline, sections merged into
house style (one Added, one Fixed).
Ships the #1101 stale-"ready" race fix (0.3.19 users are hitting it today), the
in-app uninstaller (#1099), and the Linux/Windows backend-log-dir fix.
test_app_version.py lockstep: 6 passed. bun install --frozen-lockfile: clean.
Co-authored-by: mergetest <nizam4103@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
package.json (single source of truth) + the three toolchain mirrors
(Cargo.toml, pyproject.toml, core/version.py) to 0.3.19; Cargo.lock + uv.lock
regenerated (version lines only). CHANGELOG [Unreleased] renamed to
[0.3.19] — 2026-07-12 with the release headline; all six entries carry their
(#PR) refs and extract cleanly as the GitHub Release body.
Rebuilt on current main so the freeze covers everything that landed after the
first cut: #1088 (streaming preview), #1097 (uninstaller), #1098 (stream-drop
honesty), plus the backfilled #1088 changelog entry.
test_app_version.py lockstep: 6 passed. bun install --frozen-lockfile: clean.
Co-authored-by: mergetest <nizam4103@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Removes ~110 files of process noise (all preserved in git history):
.planning/ (GSD-era phases/quick-plans/issue-clusters; workflow retired
2026-07-08), specs/ (spec-kit specs for shipped features 001-007),
design/ (pre-React ASCII mockups), research/ (legacy Gradio archive),
and .agents/ (rules for a third-party agent tool no longer in use).
The four load-bearing decision docs move to docs/adr/ with an archival
note; every live pointer follows (gguf engine module docs + quant_map,
inject-apprun.sh, pyproject/test comments, fixture README + its seed
script — kept byte-identical). The CJK allowlist drops the deleted
legacy_gradio entries; STRUCTURE.md and ROADMAP.md document the removal
instead of linking into it.
Backend suite: 2891 passed.
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Four pieces of test debt, root-caused and hardened:
1. exports.py test coverage (was: zero dedicated tests): new
tests/test_exports_api.py (26 tests) covering /export, /export/record,
/export/history, /export/reveal — happy paths, traversal/containment
guards (incl. symlink escape), destination validation, error mapping,
and the mp4 watermark-overlay branch with its plain-copy fallback.
Two real bugs found and fixed in the router:
- _safe_destination checked isabs() on realpath()'s output, which is
always absolute — dead check; a relative destination silently exported
to a cwd-dependent location instead of the documented 400.
- _safe_source let "." / ".." through the basename guard (caught only
later by realpath containment as a confusing 404); now 400 up front.
2. CI-Linux fp16 default-dtype leak (test_prefers_vocals_over_mix,
test_final_dub_track_and_seg_wav_are_watermarked): not reproducible on
macOS — instrumenting torch.set_default_dtype across both tests records
zero non-fp32 sets locally. Both tests now carry an opt-in
torch_dtype_isolation fixture (save/restore, so the leak can never
spread), and the conftest guard is demoted to pure insurance. A cheap
permanent recorder wraps torch.set_default_dtype /
set_default_tensor_type once torch appears and captures the setter's
stack only on a non-fp32 set; both fixtures print that stack when they
fire, so the next CI occurrence names the exact culprit call chain.
3. Test-order pollution (both reported combos): root cause was
collection-time sys.modules stubbing in backend/tests — seven modules
installed bare ModuleType stubs for core.config (and test_capture_ws.py
for services.model_manager/asr_backend/ffmpeg_utils, now all lazily
imported by the router anyway). pytest imports test modules during
collection, so the stubs leaked process-wide before any test ran:
- combo (a): monkeypatch.setattr("core.config.OUTPUTS_DIR", ...) in
test_longform_e2e died with AttributeError (core never gets a .config
attribute when the import is satisfied straight from sys.modules).
- combo (b): test_router_smoke's `from main import app` died with
ImportError: cannot import name 'find_ffmpeg' (unknown location).
Fix at source: new backend/tests/conftest.py sets a hermetic
OMNIVOICE_DATA_DIR (mirroring tests/conftest.py, #878) and the real
core.config is imported everywhere — zero sys.modules surgery. New
backend/tests/test_no_module_stubs.py guards the whole class (verified
fail-before/pass-after against the old stub). Stale rationale comments
in pyproject.toml and ci.yml updated to match.
4. batched_tts.py TODO(#312): investigated, comment corrected only —
#312 is closed (the live routes are engine-aware); this module has zero
call sites and stays an unintegrated experiment. See PR notes.
Full tests/ suite: 2796 passed. backend/tests standalone: 130 passed.
Both pollution combos re-run green in the reported orderings.
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
New conservative, idempotent pre-TTS normalization pass
(services/text_normalization.py): strips zero-width/control junk, caps
pathological repeat runs, expands digits/times/ordinals/currency via
num2words (29 locales) and per-language abbreviation maps (EN/DE/ES/FR).
Wired once at each text-to-engine choke point — /generate, dub segments
(+ preview), and longform chapters — BEFORE the pronunciation dictionary
so user respellings stay the final say. Pref-gated
(text_normalization_enabled, default ON) with OMNIVOICE_TEXT_NORMALIZATION
env override; num2words promoted to a direct dependency.
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
19-issue GitHub sweep: 16 PRs merged since v0.3.11, most fixing reports
filed same-day or in the days prior. Highlights: MLX-Audio's 6 other
curated models are finally selectable (was silently stuck on Kokoro
regardless of what was downloaded), first-run no longer dead-ends behind
restricted networks or corporate TLS-inspecting proxies, dubbing/batch
TTS honor your active engine selection, and a run of sharp community
diagnoses (ROCm wheel index, Windows dictation focus-steal, a genuine
frontend crash regression) got fixed largely because reporters did the
hard diagnostic work themselves.
Full backend suite: 2390 passed, 0 failed. Full frontend suite: 918
passed, 0 failed. Version lockstep (tests/test_app_version.py): 6/6
passed. Docker frozen-lockfile parity (bun install --frozen-lockfile):
clean, no drift.
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Windows users behind a corporate/antivirus TLS-inspecting proxy got a raw
`[SSL: SSLV3_ALERT_HANDSHAKE_FAILURE]` on every model install — the TCP
connection reaches the server fine, but the handshake fails because the
OS trusts the proxy's re-signed root CA and Python's bundled certifi CA
list doesn't. A genuinely different failure mode from #984 (that was
TCP-level unreachability to a blocked host, before any TLS negotiation).
- backend/core/failure.py: new SSL_HANDSHAKE_FAILURE classification
(handshake/cert-verify-failed/sslv3_alert/sslcertverificationerror
substring markers) with an actionable hint, added to
_CONTEXT_FREE_HINT_CLASSES so append_hint() (already called by
setup/download.py's install worker) surfaces it without further wiring.
- backend/main.py: truststore.inject_into_ssl() at module level, before
any huggingface_hub/requests/httpx network I/O — patches ssl.SSLContext
to verify against the OS trust store instead of only certifi's bundled
CA list. Not platform-gated (correctness improvement everywhere);
wrapped in try/except so it never blocks startup.
- pyproject.toml/uv.lock: truststore>=0.9 — pure Python, MIT, PyPA-
maintained, zero transitive deps, same class of fix as socksio.
Verified: uv lock --check + uv sync --frozen clean (lockfile diff is
just the one new package); main.py imports cleanly; full backend suite
passes; no hiddenimports entry needed (main.py is PyInstaller's direct
entry script per backend.spec, so a top-level import traces normally —
unlike socksio's case, which was httpx's internal lazy import).
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
package.json + three mirrors -> 0.3.11 in lockstep; Cargo.lock/uv.lock/
bun.lock regenerated; CHANGELOG [Unreleased] -> [0.3.11] — 2026-07-05
with the multi-language-release headline; nine entries since v0.3.10.
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(net): SOCKS-proxy users can synthesize again — ship socksio, cache-first model resolution, degrade LLM clients (#959)
Under ALL_PROXY/HTTPS_PROXY=socks5:// without socksio installed, httpx
raises ImportError AT CLIENT CONSTRUCTION ("Using SOCKS proxy, but the
'socksio' package is not installed"). huggingface_hub's get_session()
builds exactly that client inside snapshot_download, so POST /generate
500'd with the bare message even for a fully installed model, and
preload_model's model_info probe hit the same error and silently
skipped warm-up. Latent since v0.3.5 — #947's fresh-process engine
spawning unmasked it in v0.3.10 by handing the user's proxy env
directly to a clean backend process.
Three layers, so the class (any session-construction failure) is dead,
not just the reported instance:
* Ship SOCKS support: socksio>=1.0 in [project] dependencies (pure
Python, MIT, zero transitive deps) AND in backend.spec hiddenimports
— httpx imports it lazily in try/except, so PyInstaller's tracer
misses it and the frozen installers would stay broken without the
explicit entry. uv.lock regenerated; `uv lock --check` and
`uv sync --frozen` (the Docker/release bootstrap semantics) verified.
* Cache-first model resolution: from_pretrained's snapshot resolution
extracted into _resolve_snapshot_dir() — local dir, else
snapshot_download(local_files_only=True) (a complete cache resolves
with NO HTTP session constructed), else the original network path.
preload_model's failed network probe now falls back to a cache-only
check and warms up anyway instead of silently skipping (honest log
either way).
* Class guards: resolve_skill_client wraps OpenAI() construction —
env-shaped construction failures degrade to the existing "LLM
unavailable" contract instead of 500ing the calling feature; and
core.failure learns SOCKS_PROXY_SUPPORT_MISSING with an actionable
hint, appended on the raw-string surfaces (global 500 handler,
model-install SSE) via the new append_hint().
Fail-before/pass-after verified by reverting the fix: 11 of the 12 new
tests fail pre-fix (the remaining one is the unchanged network-fallback
contract). 165 tests green across the touched suites.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(changelog): add SOCKS-proxy resilience under [Unreleased] (#966)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
package.json (source of truth) + the three mirrors -> 0.3.10, in lockstep;
Cargo.lock/uv.lock/bun.lock regenerated (one line each; bun --frozen-lockfile
verified). CHANGELOG [Unreleased] -> [0.3.10] — 2026-07-05 with the release
headline; nine fixes since v0.3.9, mostly same-day field-report turnarounds.
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Revert "chore(version): main -> 0.3.9 after v0.3.8 release"
This reverts commit 7489bef085.
* chore(release): gate the post-release version-bump behind AUTO_VERSION_BUMP (owner controls bumps)
Owner decision (2026-07-01): keep main pinned to the released version and bump
only on explicit request. The version-bump job now runs only when the repo
variable AUTO_VERSION_BUMP == 'true' (default off), so releasing no longer
auto-rolls main to +1. Documented the override in CLAUDE.md's versioning rule.
---------
Co-authored-by: mergetest <test@local>
* feat(dictation): live local dictation via sherpa-onnx + Voice settings panel
Add a sherpa-onnx ASR engine alongside the existing Whisper/NeMo dictation
path, powering a genuinely live experience: as you speak, words type straight
into the focused field (streaming partials via a new simulate_type command,
self-correcting with backspaces) and commit per pause.
Backend:
- SherpaDictationBackend + sherpa_dictation registry of the 7 models (Parakeet
TDT v3/v2, streaming Zipformer EN/ZH/bilingual, Paraformer bilingual, Whisper
Tiny) from csukuangfj/* int8 HF repos; CPU provider for cross-platform parity.
- /dictation/models + /dictation/prefs router; get_capture_asr_backend() honors
the selected dictation model. get_active_asr_backend() (dub transcription) and
the legacy WebM/Opus capture path are untouched.
- True streaming over /ws/transcribe (OnlineRecognizer: live partials +
per-endpoint finals); offline models surface partials via short re-decode.
Frontend:
- New "Voice" settings panel (enable, Toggle/Hold mode, model picker with
offline/streaming/recommended badges + per-model download/delete).
- Live word-by-word typing via simulate_type (enigo) with prefix-diff delta and
backspace correction; paste fallback retained, no double-insertion.
Deps: sherpa-onnx>=1.13.3 (+ sherpa-onnx-core); uv.lock regenerated, Docker
frozen-install verified. API route-inventory snapshot updated. 40+ new tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(dictation): register sherpa-onnx-asr engine in README + features inventory
Fixes the docs-drift CI guard: the new sherpa-onnx-asr ASR engine existed in
the registry but not in docs/features.yaml or README. Adds the live-dictation
engine row to the ASR Engines table, bumps the engine counts (8→9), and adds
the inventory entry.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(changelog): fold live-dictation into the [0.3.8] section
main is 0.3.8 (untagged), so the dictation feature belongs in that release, not
a separate [Unreleased] block. Merge the two Added lists under one [0.3.8],
refresh the headline to lead with live dictation, and correct the capture
description to reflect live word-by-word typing (not paste-on-pause).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cinematic dub refinement, glossary auto-extract, and LLM-based translation all
`from openai import OpenAI` (services.llm_backend / translator / dub_translate /
glossary), but `openai` was declared nowhere in pyproject — not in dependencies,
not in any optional extra, and no setup script installed it. So a fresh `uv sync`
never installed it, and these features were dead-on-arrival on every source
install: picking Cinematic showed "Cinematic needs an LLM" even with Ollama
running and correctly configured, because `OpenAICompatBackend.is_available()`
returned "openai package missing". The UI's "pip install openai" hint is a trap
on a managed venv — users (Discord report) installed it into system Python, not
the app's `.venv`, so it still didn't take.
Add `openai>=1.40` to dependencies (resolves to 2.41.1; verified the code's
`OpenAI(...)` + `chat.completions.create(model=, messages=)` call shapes are
unchanged in 2.x). Pure-Python, no native deps → identical on macOS/Windows/Linux
(default-parity rule). Cinematic + any OpenAI-compatible endpoint (OpenAI, Ollama,
LM Studio, vLLM) now work after `uv sync`, no manual package install.
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The audiobook importer accepted .txt/.md/.epub but not PDF — the single most
common "ebook in" format. Add a pure `pdf_to_chapter_script(data)` that
extracts the text layer page-by-page and runs it through the existing
chapterizer, so PDFs land in the same `# Heading` + body grammar EPUB and
plaintext already produce (one front door onto the unchanged render pipeline).
- Dep: `pypdf>=4.0` — pure-Python, MIT, zero native deps, so PDF import behaves
identically on macOS/Windows/Linux (default-feature cross-platform rule).
EPUB + plaintext stay stdlib-only; only PDF needs a real parser.
- Robustness, surfaced as actionable 400s rather than silent empty imports:
corrupt file, password-protected (empty-password decrypt attempted first),
scanned/image-only (no text layer → clear "scanned PDF" message), and a
page-count ceiling. A single unparseable page is skipped, not fatal.
- Route: `.pdf` branch in audiobook_import; frontend accept filter +
api-client doc updated to `.txt,.md,.epub,.pdf`.
tests/test_longform_import.py: 5 PDF cases (extract+chapterize, no-marker
single chapter, corrupt, image-only, page-cap) using a hand-built in-memory
PDF — no PDF-authoring test dep, mirroring the in-memory-EPUB approach.
16 passed; frontend suite 401; CJK guard green.