454affb6e9efe05e8379e5d436392b497a3f6708
100
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
454affb6e9 |
feat(tts): unlimited-length generation — sentence-boundary chunking + crossfade (Wave 1.2) (#357)
Ports voicebox's chunked TTS (MIT, attribution header) with two deliberate changes: the concat half is reworked for torch tensors (matching what our inference helpers feed the effect chain, incl. multi-channel on the last axis), and the sample rate comes from the engine's declared rate instead of the first chunk (fixes a latent upstream bug). Long text (> max_chunk_chars, default 800) splits at sentence boundaries (abbreviation/decimal-aware, bracket tags atomic, fullwidth enders via unicode escapes for the CJK gate) -> per-chunk generation with deterministic seed variation (seed+i) -> linear crossfade join (default 50 ms, 0 = hard cut) -> effect chain + watermark once on the joined audio. Wired into BOTH inference paths (OmniVoice-native _run_inference and the engine-adapter _run_backend_inference) beside the existing [pause] stitcher; [pause] inputs keep their dedicated path. Short text is byte-for-byte the old single-shot path; max_chunk_chars=0 disables. New /generate form params: max_chunk_chars (>=0, default 800), crossfade_ms (0-1000, default 50). Tests: 15 model-free unit tests (split priorities, abbreviation/decimal/ tag guards, crossfade math incl. multichannel + clamping) + 3 stubbed- engine endpoint tests (long text fans out with no words lost, short text single-shot, 0 disables). Endpoint tests validated in CI — this machine has a pre-existing local torch/Triton segfault on any main-importing test. Spec: voicebox deep dive 1 / parity program Wave 1.2 / #346 unlimited-length item. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
93723c2789 |
feat(dictation): collapse Whisper hallucination loops in final transcripts (Wave 1.1) (#356)
Deterministic pre-pass ported from voicebox (MIT, attribution header): word-level (token repeated >=6x, punctuation-normalized) + character-level (2-60-char unit repeated >=6x, catches multi-word and no-space-script loops). Rhetorical repeats below 6 survive; no LLM involved; identical on every platform. Applied to the FINAL text in /ws/transcribe and POST /transcribe — segments keep raw recognition so timings stay truthful. Phase 1 of Spec 3 (docs/competitive-analysis.md); the optional local-LLM refinement pass (phase 2) lands with parity program Wave 2.1 in the same module. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
7422f20a63 |
feat(profiles): consent-locked voice profiles — verified_own_voice + spoken consent flow (Wave 0.2) (#354)
* feat(profiles): consent-locked voice profiles — verified_own_voice + spoken consent flow (Wave 0.2)
A profile becomes 'verified own voice' when its owner records themselves
reading a consent statement (spoken attestation, not a checkbox). Agentic
features and gallery sharing will gate on the flag; plain local synthesis
never does.
- alembic 0003 (additive, PRAGMA-guarded, downgrade supported) +
_BASE_SCHEMA columns: verified_own_voice, consent_text,
consent_audio_path, consent_recorded_at
- POST/DELETE /profiles/{id}/consent — stores the recording as provenance
in VOICES_DIR ({id}_consent.*), replaces on re-record, cleans up on
revoke and on profile delete; 422 on empty statement / too-short audio
- VoiceProfile page: Verified badge + Voice ownership panel (record via
the existing useRecording denoise flow, revoke with confirm); en.json
keys only (other locales fall back per the advisory i18n parity policy)
Spec: docs/competitive-analysis.md Action 22 / parity program Wave 0.2.
Prerequisite for agentic v2/v3 and the persona gallery.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(profiles): harden consent paths against py/path-injection; drop lifespan in tests
- _voices_path(): resolve DB-stored filenames strictly inside VOICES_DIR
(bare-filename check + realpath containment); extension whitelist on the
uploaded consent filename (fallback .wav) so a crafted filename can never
steer the on-disk path. Applied to write, re-record cleanup, revoke, and
profile-delete cleanup. New test: malicious upload filename falls back.
- Test fixture no longer runs the app lifespan: startup/shutdown touched
module-level asyncio primitives bound to another module's event loop,
making the suite order-dependent in full-suite CI. init_db() is called
directly; endpoints under test need only the schema.
Fixes the CodeQL (3x py/path-injection high) and full-suite event-loop
failures on PR #354.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
1195b4e0dd |
test(evals): LLM-judge eval tier — non-gating semantic suites (Wave 0.3) (#355)
Ports Patter's eval harness (MIT, attribution headers) into tests/evals/ with the judge transport swapped to services/llm_backend.py — the judge runs against whatever local Ollama/LM Studio/OpenAI-compat endpoint the user configured, keeping local-first. Both Patter hardening details kept verbatim: verdict recomputed locally from the score (hallucinated 'passed: true' at score 0.2 fails), and tolerant JSON parsing (fences stripped, invalid JSON -> fail-with-reasoning). Per-case containment: agent exceptions keep the partial transcript and still judge it; a judge failure records score 0 instead of aborting the suite. HARD RULE preserved: LLM judges never gate CI. The scheduled workflow (weekly + dispatch) is continue-on-error with the JSON report as artifact; run_evals.py exits 0 always and skips cleanly when the active LLM backend is 'off'. Deterministic probe judges remain the only gates; the harness unit tests (10, no LLM needed) do run in gating CI. First suite: dub translation naturalness v1 (4 cases) driving the real cinematic_refine_sync reflect+adapt chain. The telephony-specific session/assertions layers were deliberately not ported. The dictation-refinement suite lands with Wave 1.1/2.1. Spec: docs/competitive-analysis.md Spec 9b / parity program Wave 0.3. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
11c498eeb5 |
ci(docs): daily docs-drift job — canonical inventory vs README/docs/registries (Wave 0.1) (#353)
docs/features.yaml is the curated single source of truth (12 features, 11 TTS + 7 ASR engine ids, required install docs). scripts/check-docs-drift.py diffs it against README.md, docs/, and the engine registries — parsing registry keys from source so the CI runner never imports torch. The daily workflow updates ONE rolling 'docs-drift' issue in place and auto-closes it when clean (pattern adapted from Patter, MIT). Self-test includes a real-repo-is-clean gate, so any PR that changes engines/features without updating the inventory fails CI too. Spec: docs/competitive-analysis.md Spec 9a / parity program Wave 0.1. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
73de4f9277 |
docs(specs): ElevenLabs-parity program — waved implementation plan from #346 + #345 (#349)
Turns the discussion #346 roadmap and the competitive-analysis research (#345) into an executable program of small PRs: 6 waves, dependency-aware, each item citing its Spec/§R section with effort and acceptance criteria. Accounts for Smart Fit Phase A (#347), the timeline editor (#348), and Scalar (#307) having already shipped. Telephony explicitly deferred behind guardrails + two spikes. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
eea2053a5e |
docs: competitive analysis v2 — second-tier landscape, source deep dives, action specs, market sentiment (#345)
* docs: expand competitive analysis — second-tier landscape, deep dives, action specs, market sentiment Second research pass over PR #339's analysis (six parallel agents): - Second-tier landscape: 13 projects surveyed, 7 profiled; KrillinAI/KlicStudio promoted to direct-competitor status - Source-level deep dives: voicebox + Patter (MIT, portable briefs) and pyvideotrans (GPL, clean-room functional specs incl. the full _rate.py decision tree with verified constants) - pyvideotrans's OmniVoice integration verified broken (Gradio /_clone_fn vs our FastAPI :3900) — Action 11 reframed as fix-the-bridge - Implementation specs mapping all ranked actions onto our codebase - User-sentiment + market-positioning research (issue clustering, ElevenLabs pricing pressure, honest verdicts on our five differentiators, name-collision risk, four positioning moves) - Three stale matrix grades corrected (docs-drift CI, eval harness, MCP) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: ground the #346 roadmap in research — agentic voice, remote GPU, audiobooks, persona gallery, model/env management Third research pass (four agents + five verification sub-agents) adding a 'Roadmap directions' section that maps every item from discussion #346 to either an existing spec or new research: - Agentic voice workflow: pipecat (BSD-2) as the license-clean in-process runtime; honest telephony constraints (no local PSTN path — opt-in carrier creds only); FCC/TCPA, Texas SB 140, ELVIS Act, EU AI Act Art 50 (2026-08-02, OSS exemption does not cover it); six concrete guardrails; v1/v2/v3 scope ladder - Remote GPU/Tailscale/remote API: base-URL + bearer-token consensus pattern; 175k-exposed-Ollama cautionary tale; Tailscale rung (a) docs-only; vLLM drop-in for llm_backend; Scalar already shipped (#307), remaining work is OpenAPI hygiene - Audiobook creator + persona gallery: ACX technical-spec mastering bar; ebooklib/PyMuPDF/mobi AGPL/GPL parser traps with clean alternatives; unoccupied consent-aware-gallery territory; .ovsvoice portable format - Model/env + GPU compat: uv link-mode dedupe math (measured wheel sizes); two-dimensional (torch x cuda-variant) -> sm_XX compat matrix; HF cache as single source of truth (hf cache ls/rm/verify); preflight gate + loud CPU-fallback banner vs the Ollama/voicebox silent-fallback antipattern - Eight consolidated new actions (15-22) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
65fc5245dc |
feat(dub): timeline segment editor — drag, snap-to-onset, keyboard a11y (#280) (#348)
* feat(dub): full-track speech-onset detection + GET /dub/onsets/{job_id} (#280)
detect_speech_onsets() lists every speech rise across the track (frame RMS,
adaptive threshold, 150ms hysteresis) — powers the timeline editor's
snap-to-onset ticks. Route prefers the Demucs vocals stem, falls back to the
mix, and caches onsets.json per job (mtime-invalidated).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(dub): timeline editor math core — windowing, snap, clamp, fingerprint-safe commit (#280)
Pure helpers for the segment track: binary-search windowing, snapTime with
deterministic ties, neighbour/min-duration clamps with Alt-overlap (<=200ms),
commitMoveResize with fingerprint parity (move touches only start/end; resize
sets speed exactly like the old Regions handler and DELETES the key at 1.0 so
_canon_value's missing-vs-1.0 hashing can't mark untouched segments stale),
and overlap detection.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(dub): SegmentTrack editing lane replaces the Regions plugin (#280)
Custom DOM segment boxes (6px edge handles, body-drag move, speaker colors,
stale/fresh tint, hatched overlap warning) virtualized by time over a single
{pxPerSec, scrollLeft} alignment source read off WaveSurfer's wrapper.
Snap-to-onset ticks on a viewport-sized canvas light up in snap range;
Ctrl/Cmd-wheel zooms centered on the cursor; double-click plays the slot via
playRange (timeupdate watcher pauses at slot end). Roving-tabindex listbox
keyboard model (arrows / Enter / Shift / Alt / Delete / S) with polite
aria-live announcements. WebKit fallback keeps a self-scrolling lane at a
fixed px/sec. timeline.* strings translated in all 21 locales.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(dub): wire timeline editor — per-gesture undo, id fix, table selection sync (#280)
segmentMoveResize() pushes undo ONCE per gesture (drag commits on pointerup;
keyboard nudges coalesce per focus session) and matches by String(id) — the
old parseInt('seg-3_a') path edited the wrong segment after a split. Commits
go through commitMoveResize for fingerprint parity, and the existing
recomputeIncremental effect picks up every commit. Clicking a timeline box
scrolls + highlights its row in DubSegmentTable; 'preview dub here' parks
the player at the slot start, then synthesizes the line.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(dub): inline the onsets-cache containment guard — CodeQL can't track helpers
Same lesson as #328/#329: the realpath+startswith sanitizer must sit at
the sink, not behind a function return. Unused helper removed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
4b21f82619 |
feat(dub): Smart Fit timing strategy — planner, fingerprints, generate path (phase A) (#347)
* feat(dub): Smart Fit planner, fit fingerprints, shared ffmpeg stretch helpers - services/fit_planner.py: pure, I/O-free planner for dub-length fitting v2 — slack absorption (gap guard), audio-only band (<=1.2x), geometric 50/50 audio/video split capped at 1.5x / 2.0x, residual overflow accounting, and a stretch_video-compatible video_plan + fitted timeline cursor. Clean-room reimplementation from a published description. - services/incremental.py: fit_fingerprint() over the fit params with the same _canon_value canonicalisation as segment hashes (#281 class). Fit params stay OUT of segment_fingerprint — a fit change re-mixes, never re-TTSes. - services/ffmpeg_utils.py: move _atempo_chain/_pitch_preserving_stretch out of the dub_generate router (lazy torch/numpy imports) so the Phase B export pipeline can reuse them; add probe_duration() ffprobe helper. - schemas/requests.py: timing_strategy gains "smart_fit"; optional fit_options knob overrides default server-side. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(dub): smart_fit branch in the generate path TTS loop unchanged (dur_s=None, natural-rate WAVs on disk). After the loop, plan_fit() decides per segment; the mix loop applies audio_rate via the pitch-preserving atempo pipe (linear-interp fallback), trims residual overflow with the existing fades, and places audio at the planned new_start on a fitted-length canvas. Truthful fit_status entries (audio_rate / video_ratio / overflow_s) feed the row badges. Persists job["fit_plans"][lang] = {plan (exact _build_video_stretch_filter_graph shape), fitted_segments (cue times from ACTUAL stretched sample positions), total/orig duration, params, fit_fp} and mirrors fit_fp on dubbed_tracks[lang]. video_stretch_plans untouched. Strategy-transition guard: job["seg_wav_kind"] records whether on-disk seg WAVs are natural or slot-squeezed; a smart_fit partial regen over slotted (or unknown) WAVs forces one full regen instead of double-compressing. Old strategies and old persisted jobs are byte-identical (all new reads via .get()). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(ui): Smart Fit option in the dub timing picker (all 21 locales) - prefsSlice: TimingStrategy union gains 'smart_fit'; optional FitOptions overrides (null by default — backend defaults apply identically on every platform); persisted alongside timingStrategy. - DubTab: Segmented gains Smart Fit with i18n label + tooltip. - useDubWorkflow: sends fit_options only when set and strategy is smart_fit. Default strategy stays 'concise' — no default behaviour change on any platform. - locales: dub.timing_smart_fit{,_title} translated in all 21 languages. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(dub): fit planner unit + golden suites, smart_fit generate-path integration - test_fit_planner.py: threshold boundaries (0.9/1.0/1.2/1.21/4.0), cap saturation -> overflow, slack absorption incl. gap guard, last-segment tail, cursor monotonicity, allow_video_retime=False, video_plan fed straight into _build_video_stretch_filter_graph, fit_fingerprint canonicalisation (int vs float, omitted vs default — the #281 class) and a pinned stable digest. - tests/fixtures/fit_planner/*.json: 4 golden FitPlans; algorithm drift is a deliberate fixture diff, never a silent change. - test_smart_fit_generate.py: hermetic end-to-end runs (mock TTS, no ffmpeg) covering audio-only stretch, hybrid timeline growth + persisted plan shape, fit_options override, strict_slot->smart_fit forced regen then zero-TTS fit-only re-mix, and concise back-compat. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(competitive): dub-length fitting row reflects Smart Fit Phase A Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(incremental): mark fingerprint hashes usedforsecurity=False — dedup keys, not security (Bandit) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: mergetest <test@local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4288863f50 |
docs: model-source support policy — verifiable public sources only (#310) (#344)
* docs: model-source support policy — verifiable public sources only Owner decision (issue #310): the local-loading mechanism stays, but official support covers only models from verifiable public sources (HF repos, official releases with license + checksums). Privately distributed / paywalled model files are use-at-your-own-risk; never run bundled executables. Mirrored in SECURITY.md as a supply-chain note. Per the docs-sync rule, shipped alongside the policy decision. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: firm up model-source policy — open, public, verifiable only; no private/paid models Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: mergetest <test@local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
2574fccaf6 |
docs: community docs refresh — README, CONTRIBUTING, SECURITY, SUPPORT, Docker/macOS install (#341)
* docs: refresh community docs to match the project's current reality - README: download badges now point to releases/latest (were frozen at v0.2.7); Intel-Mac note (pre-built bundle is Apple Silicon; source works on Intel; pre-built Intel tracked in #279) - SECURITY: supported-versions table 0.2.x -> 0.3.x + 0.2.7 legacy row - docs/install/docker.md: tag mapping matches docker.yml after #338 — :latest is the rolling main preview, :stable (new) pins releases - PR template: removed the abolished two-RC/48h-soak ceremony; documents continuous-to-main - CONTRIBUTING: new sections — what bot review looks like (CodeRabbit + Greptile), conventional-commit + issue-link expectations, the quality gates (cross-platform parity, 21-locale i18n + CJK allowlist, alembic, engine back-compat, local-first, loopback security posture), and a contribution-licensing grant that keeps the AGPL + commercial dual-license viable - SUPPORT.md: new — channels, before-you-file checklist, expectations - docs/install/macos.md: Intel caveat aligned with reality Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: codify the docs-sync hard rule — behavior changes update their docs in the same PR Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(agents): rtk rules for Antigravity — token-compressed tool output Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: mergetest <test@local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
101cf2a6e7 |
ci(release): reinstate macOS Intel (x86_64) build target on macos-15-intel (#342)
Intel MacBook users had no installable artifact: the release matrix only built aarch64-apple-darwin, and Rosetta 2 cannot run arm64 apps on Intel (it only translates the other direction) — the rationale in the old "Intel dropped" comment was backwards. Refs #279. - Add a native `macos-15-intel` matrix leg (GitHub's designated x86_64 migration target after macos-13 retired Dec 2025; standard image, supported through Aug 2027) building --target x86_64-apple-darwin with app,dmg,updater bundles. - Existing per-TRIPLE steps already carry x86_64-apple-darwin cases (uv sidecar tar.gz, evermeet.cx ffmpeg/ffprobe — x86_64 Mach-O, natively correct on Intel), so the leg flows through the same Bundle/Build/Smoke/Verify steps untouched. - The PR #290 signing path applies automatically: ad-hoc seal from tauri.conf.json signingIdentity "-", opt-in APPLE_* stable signing, and scripts/verify-macos-signing.sh both gated on runner.os == macOS. - tauri-action includeUpdaterJson merges the new darwin-x86_64 platform key into latest.json alongside darwin-aarch64, so Intel installs auto-update on both Stable and Preview channels. - docs/install/macos.md: table telling users which DMG (aarch64 vs x64) matches their Mac, and the from-source fallback for old releases. Co-authored-by: mergetest <test@local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
acb7c90083 |
fix(tts): pin cudagraph-compiled model inference to one dedicated thread (#315) (#343)
torch.compile(mode="reduce-overhead") captures CUDA graphs whose state is thread-local (torch/_inductor/cudagraph_trees keys its tree manager off the capturing thread). The _gpu_pool ThreadPoolExecutor runs up to 4 workers, so the first render captured the graph on worker A and a later render dispatched to worker B replayed mismatched cudagraph state — silently corrupting the audio (static noise + slowed playback from the second render onward, no exception, so the #327 eager fallback never fired). Fix: when the model is compiled with a cudagraph mode, wrap model.generate (the same single choke point #327 uses) so every call hops to a dedicated 1-thread "compiled-infer" executor — capture and replay always happen on the same thread, deterministically. A thread-ident re-entrancy guard runs inline when already on that thread (a 1-worker executor submitting to itself would deadlock). Installed after the #327 fallback wrapper, so the eager retry path also runs on the dedicated thread. No behavior change for CPU / MPS / Windows-no-Triton / compile-disabled paths: should_torch_compile() gates exactly as before and uncompiled models keep the full pool. Closes #315 Co-authored-by: mergetest <test@local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
948bc76543 |
macOS: ad-hoc sign so users open without Terminal + signing/notarization verification (#290)
* chore(release): add macOS signing/Gatekeeper/notarization verification Codify and enforce the macOS build-signing requirements. The release pipeline built bundles and had opt-in Apple signing, but never verified codesign/spctl/notarization — unsigned or broken bundles could ship silently. - scripts/verify-macos-signing.sh: runs codesign --verify --deep --strict, spctl Gatekeeper assessment, per-nested-Mach-O signature check, stapler validate, and (opt-in) notarytool history. Report-only by default (unsigned dev/preview is expected); --require-signed fails on any unsigned/un-notarized component so a broken release stops instead of publishing an unsigned artifact. - scripts/macos-dev-unquarantine.sh: local-dev-only quarantine stripper, with a loud "never a substitute for notarization" warning. - release.yml: new "Verify macOS signing" step on the macOS leg — report-only on unsigned paths, STRICT on the opt-in signed stable path (same condition as "Configure Apple signing"), so signing/notarization failures fail the job. - docs/macos-signing-verification.md: the canonical 10-point requirements + how-to-verify checklist, cross-linked to docs/install/macos.md and DESKTOP_RELEASE.md. Verified locally: report-only PASS (exit 0) and --require-signed FAIL (exit 1) against the real unsigned debug .app; release.yml parses as valid YAML. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(macos): ad-hoc sign bundle so users open it without Terminal (no Apple ID) The "app is damaged and can't be opened" error is caused by a broken/incomplete code-signature seal (codesign --verify failed: "code has no resources but signature indicates they must be present") on the quarantined download — there is no GUI bypass for that variant on modern macOS, forcing users to run `xattr`. Give the bundle a VALID ad-hoc signature at build time (free, no Apple Developer account) via tauri.conf.json bundle.macOS.signingIdentity = "-". Verified through a real `tauri build`: the produced .app is now flags=adhoc,runtime and passes codesign --verify --deep --strict. A valid seal flips the Gatekeeper prompt from the un-bypassable "damaged" to the GUI-bypassable "unidentified developer", which users clear with right-click → Open / Settings → "Open Anyway" — no Terminal. Still not notarized (that needs the paid Apple ID), so there's a one-time confirmation rather than a clean double-click. The opt-in Developer-ID path is unchanged: APPLE_SIGNING_IDENTITY (env) overrides the "-" default on the signed stable release. - tauri.conf.json: signingIdentity "-" (ad-hoc default). - verify-macos-signing.sh: detect ad-hoc tier; report the no-Terminal GUI path in report-only, still FAIL it under --require-signed (production must notarize). - docs/install/macos.md: lead the Gatekeeper section with right-click → Open; keep xattr as fallback for the harsher "damaged"/corrupted-download case. - docs/macos-signing-verification.md: signing-tiers table + ad-hoc default note. - release.yml: comment the ad-hoc default + env override on the signed path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
3c780dced9 |
feat(dub): speech-onset alignment + regional dialect targeting (#280) (#330)
Items 1 and 2 from the improvement list: 1. Synchronization — Whisper-family ASR stretches segment starts back over leading non-speech (intro music, silence), so the dub starts at 0:00 while the speaker starts at 0:02-0:03. New onset_align service snaps each segment start forward to the first audible vocal onset (adaptive RMS threshold over the Demucs-isolated vocals when available). Forward-only and conservative: never moves a start earlier, ignores sub-100ms shifts, preserves minimum duration, leaves silent-window segments untouched. Pure NumPy — identical across platforms. 2. Accent/vocabulary by country — a Dialect picker in the Dub panel (BCP-47 codes per target language) injects a regional instruction into LLM translation prompts (OpenAI/Ollama engines and the Cinematic refine pass): Argentina yields 'Vos sos muy listo', not 'Tú eres muy listo'. Non-LLM engines show a clear hint that the dialect needs an LLM. New i18n keys translated in all 21 locales. Item 3 (segment rectangles: move/crop/stretch on the timeline) is a larger editor feature and stays open on #280. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: mergetest <test@local> |
||
|
|
c0924f5eba |
fix(tts): torch.compile failures fall back to eager — generation never fails on unsupported GPUs (#278) (#327)
* fix(tts): torch.compile failures fall back to eager — generation never fails on unsupported GPUs (#278) On GPU architectures the bundled Triton doesn't support (e.g. Blackwell sm_120 / RTX 5060), the compiled model dies mid-generation inside the Dynamo/Inductor/Triton/cudagraph stack — previously surfaced as a fake 'ran out of memory' error and a dead Archetype preview. Now: - up-front arch gate: skip compile when the GPU's compute capability is not in this torch build's arch list (OMNIVOICE_FORCE_TORCH_COMPILE=1 overrides for PTX forward-compat setups) - runtime fallback: model.generate is wrapped once; a compile-stack failure (classified by exception chain: module, message, traceback paths — the cudagraph case is a bare AssertionError) logs a warning, restores the eager module, disables compile for the session, resets dynamo state, and retries eagerly. Non-compile errors propagate unchanged. - the /generate OOM handler no longer mislabels compile crashes as OOM and points users at the actual remedy. Fixes #278 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Potential fix for pull request finding 'CodeQL / Empty except' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Potential fix for pull request finding 'CodeQL / Empty except' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update backend/api/routers/generation.py Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: mergetest <test@local> |
||
|
|
e2027c1291 |
ci(security): never cancel main scans — merge trains left red ✗ on every intermediate commit (#340)
PR branches keep cancel-in-progress (superseded scans are wasted work). On main each commit gets its own concurrency group, so a burst of merges runs every scan to completion instead of cancelling all but the last — 'cancelled' renders as a permanent red ✗ in the commit history even though nothing failed. Co-authored-by: mergetest <test@local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
a949b2c78a |
chore(version): main is always latest release + 1 — rule, bump to 0.3.6, Docker retag, auto-bump job (#338)
Versioning hard rule (owner-set 2026-06-11), codified in CLAUDE.md: - main's three version sources (tauri.conf.json, Cargo.toml, pyproject.toml) always carry last release + 1 patch; bumped 0.3.5 -> 0.3.6 now. - Preview builds stamp BASE-N which now sorts ABOVE the last stable (0.3.6-N > 0.3.5) — the updater ordering becomes natural and the Windows MSI ProductVersion wrinkle disappears. - Docker: :latest = rolling main preview; :stable + :X.Y.Z + :X.Y = tagged releases. workflow_dispatch still only emits throwaway :sha-. - release.yml gains a version-bump job: on every stable v* tag it bumps main to the next patch automatically. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
853b9eefc7 |
fix(dub): burn translated subtitles, fix subtitle save JSON error (#309) (#328)
* fix(dub): burn translated subtitles, fix subtitle save JSON error (#309) Two symptoms, one root: the job kept the original-language ASR transcript while the editor only sent translated/edited text in the generate request. - dub_generate now persists the segments the dub was actually generated from back onto the job (metadata carried over by stable id, fallback index; text_original retained for dual-subtitle layouts) — SRT/VTT export and ffmpeg burn-in now render the dub language, not the source. - The SRT/VTT export endpoints honor the save_path query param the Tauri save dialog appends (like every other export) and return the standard JSON envelope — previously they ignored it and returned the raw body, so the frontend's JSON.parse choked on the SRT cue index ('Unexpected non-whitespace character after JSON'). - Frontend guards the save response content-type so any future raw-body response surfaces as a clear error. Fixes #309 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * fix(dub): use the file's established realpath+startswith containment idiom (CodeQL) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(dub): write subtitle saves from the Tauri process, not the backend (#309) The backend save_path variant on /dub/srt and /dub/vtt routed a user-controlled destination through the loopback HTTP surface — six new CodeQL path-injection flows plus two log-injection flows. Subtitles are small text bodies, so the frontend now fetches them raw and writes the file via a new save_text_file Tauri command: the OS save dialog in the trusted process is the write authorization, and the backend never sees a destination path. Binary exports keep the established save_path flow. Also strips newlines from user-derived values in the two flagged log lines. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(dub): leave _native_save byte-identical to main The newline-strip on the log line moved a path sink onto a changed line, which made CodeQL re-attribute the long-standing binary-export flow to this PR as a new alert. The subtitle endpoints no longer feed this function at all, so restore the exact original line — the baseline alert stays baseline, and hardening pre-existing flows belongs in its own PR. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> |
||
|
|
1ed22af6ca |
docs: competitive analysis — voicebox, pyvideotrans, Patter (feature matrix + ranked adoption plan) (#339)
* docs: competitive analysis — voicebox, pyvideotrans, Patter Feature matrix vs our self-inventoried maturity grades, license-aware reuse verdicts (MIT = port with attribution, GPL-3.0 = reimplement only — copied GPL files would break the AGPL + commercial dual-license), and an 11-item ranked action plan with effort estimates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: append Chatterbox engine evaluation to the competitive analysis Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: mergetest <test@local> |
||
|
|
d0517fdb87 |
chore(review-bots): diagrams + ASCII UI sketches in every PR walkthrough (#337)
* chore(review-bots): visual walkthroughs — diagrams for mechanics, ASCII sketches for UI CodeRabbit: enable sequence_diagrams explicitly and instruct the high-level summary to sketch UI changes as compact ASCII before/after and behavior changes as a small mermaid flow. Greptile: new repo-level greptile.json turning on the sequence-diagram and summary sections with matching instructions, plus the project's local-first and cross-platform hard rules so both bots review against them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(review-bots): expert-panel review rubrics, pre-merge rule audits, knowledge base Encode one senior-domain-expert lens per subsystem (ML inference for backend/services, product frontend for src, desktop systems for src-tauri, test infra for tests) as path instructions; add non-gating pre-merge checks for the project's four hard rules (cross-platform default parity, 21-locale i18n completeness, local-first guarantee, backward compatibility); feed CLAUDE.md and docs into CodeRabbit's knowledge base; mirror it all in greptile.json with customContext rules and strictness tuning. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
668d824e86 |
feat(setup): unified first-run journey — install gate, studio-console wizard, platform awareness (#295)
* feat(setup): first-run install gate — nothing installs until the user confirms a plan New `setup` module parks first runs in BootstrapStage::AwaitingSetup instead of auto-installing. complete_setup validates the user's InstallPlan and only then starts the existing bootstrap: - install modes: installed (platform dirs) / portable (one folder next to the exe / AppImage, config.json travels with it) - user-chosen storage: env dir, data dir (OMNIVOICE_DATA_DIR), model cache (OMNIVOICE_CACHE_DIR) — None = legacy default, byte-identical behavior - minimum-space gate: per-volume free-space check (fs4 statvfs), grouped by filesystem so dirs sharing a disk sum their requirements; install refused when short (9 GiB env + 7 GiB models + 1 GiB data, measured + headroom) - custom mirrors (PyPI index, HF endpoint, python-build-standalone) take precedence over region presets in the venv/sync/backend env wiring - ROCm torch variant selectable via config (env var still wins) - existing installs migrate silently: venv present → setup_complete=true, no questions re-asked; dev trees skip the gate entirely 19 unit tests (disk probing, space grouping, mirror validation, legacy config compat). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(setup): first-run setup screen — mode, storage with space gate, mirrors, compute FirstRunSetup renders when the Rust side reports awaiting_setup (lazy-loaded; regular launches pay nothing). One screen, defaults all work: - language picker first (rest re-renders translated), 21 locales shipped - Installed / Portable mode cards (portable disabled with reason when the exe-adjacent folder isn't writable) - storage rows with live per-path free-space probes (debounced check_install_target), 'needs ~X / Y free' readouts, folder pickers - client mirrors the Rust per-volume space gate: Start installation is disabled with an explicit reason until every volume fits - compute (CUDA-auto / ROCm), update channel, region + custom mirror URLs - complete_setup errors surface inline; on success the normal bootstrap progress UI takes over on the next status poll Verified on a wiped machine: gate parks (no spawn, no downloads), screen renders, 450 GB ≥ 17 GB requirement → Start enabled. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(setup): studio-console redesign of the first-run screen The setup screen now reads as powering on studio hardware rather than a web form — true to a voice studio, and self-sufficient offline (every font and asset is bundled; a first run may be on a restricted network): - breathing waveform masthead (CSS-only, deterministic speech-cadence silhouette, staggered per-bar delays) - Source Serif 4 display headline + engraved IBM Plex Mono panel labels + Inter body — the three faces the app already ships - rack-unit panels with corner screws, engraved title rules, serial plate (OVS · vX.Y.Z) - disk space as segmented LED capacity meters: lit = what the install consumes, alarm-blink red on insufficient volumes - mode cards with indicator LEDs; 'armed' Start button — LED lights and a halo pulses only once every volume passes the space gate - atmosphere: corner accent glows + SVG film grain; staggered rise-in choreography on load - all motion transform/opacity only; prefers-reduced-motion holds every frame still; theme-token derived colors; focus-visible rings throughout No logic changes: same IPC calls, same i18n keys, same space-gate math. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(setup): wide desktop deck, hardware-aware Compute + Update channel cards Three pieces of feedback addressed: - width: the console is now a 1240px two-column deck (storage rail left, decision rail right) that uses desktop real estate; collapses to one column under 980px and stacks fully under 620px - no outer chassis box: panels float directly on the atmospheric backdrop, each carrying its own rack-unit treatment - Compute and Update channel split into separate cards with real information: get_setup_state now detects hardware (nvidia-smi → CUDA name, /sys/class/drm vendor 0x1002 → AMD/ROCm, Apple Silicon → MPS, CPU cores + RAM via sysinfo; best-effort, never blocks) — the Compute card shows a live 'Detected: …' readout, badges the option that matches the machine, and pre-selects ROCm on AMD boxes; both cards use LED radio options with full descriptions (6 new i18n keys × 21 locales) Also pins playwright-core as an explicit devDep — bun did not materialize it through @playwright/test, breaking programmatic browser use. 20/20 Rust tests · vite build · CJK guard green. Verified live (gate engaged, responsive single-column) and at 1600×1000 via mocked-IPC browser shot (two-column deck). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(setup): move network (region + mirrors) into the masthead with language Language and download region are the two 'where am I' choices — they now sit together top-right of the masthead, with the custom-mirrors disclosure tucked beneath the subtitle. The Network panel is gone, leaving a balanced deck: Install mode + Storage left, Compute + Update channel right. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(setup): strip the boxes — fills and rules carry the structure One design rule now: borders only where state demands them. Panels lose their boxes entirely (engraved mono title + rule separates sections); option cards, storage rows, selects/inputs, the hw readout, the version plate and the ghost buttons are all flat fills; active options glow with an accent tint + LED; blocked rows and errors use a red tint + 2px inset edge bar instead of a border. The badge chip is fill-only too. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(setup): quiet pass — every element earns its visual weight - waveform becomes a whisper: 22px trace, 2px bars, ~half opacity — an ambient signature instead of a billboard - storage readouts collapse to one mono line ('needs ~9 GB · 449 GB free'); the LED meter now appears only when it carries information (install would consume >35% of free space, or the volume is blocked) — at 449 GB free a bar was a meaningless sliver - Change… buttons go text-quiet (transparent until hover) - custom-mirrors disclosure right-aligns under the region select it extends, instead of floating under the subtitle - version plate moves to the footer next to the disk total — the masthead keeps only title, subtitle, and the two locale/region selects Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(setup): platform-matrix awareness — distro+arch detection, ROCm gated to Linux, no Windows console flash The install matrix is OS family × distro × arch × GPU vendor, and the setup screen now both shows it and only offers choices valid for it: - HardwareInfo gains os_name (distro PRETTY_NAME from /etc/os-release on Linux, macOS/Windows elsewhere) and arch (x86_64/aarch64) — the detected line reads 'CachyOS x86_64 · NVIDIA RTX 4070 · 32×CPU · 31 GB RAM', exactly what bug reports cite - SetupState gains os; the ROCm option renders on Linux only (wheels don't exist elsewhere) and complete_setup clamps rocm→auto on non-Linux as the server-side backstop - nvidia-smi probe gets CREATE_NO_WINDOW on Windows — no cmd flash on the first screen a user ever sees - Apple Silicon → MPS, Intel mac → CPU, ARM Linux → CPU: all matrix cells resolve through the same base constructor Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(setup): unify the whole first-run journey under the studio-console system Setup → Installing → Model wizard now read as one continuous experience: the same atmosphere, whisper waveform masthead, serif/mono type, LED language and quiet fills across all three acts. - Installing (BootstrapSplash): rebuilt in frs-* — segmented LED journey meter (completed steps + live byte progress), LED step rail (done=green, active=pulsing accent, pending=dim), engraved ACTIVITY panel with the quiet mono log (collapse/copy as text-quiet actions), failure act with red-tint error + hints + armed Retry. All logic untouched: stage poll, event subscription + backfill, dedupe, hints, region/language selects. - Model wizard (SetupWizard): same masthead with the step rail as engraved mono LED steps top-right, welcome cards as option-card surfaces, preflight as LED check rows (pass/warn/fail), frs nav buttons with armed primaries, embedded Model Store / Engines / Dictation panels scroll inside the act. Old 556-line stylesheet replaced by ~60 lines of glue; BootstrapSplash.css reduced to a resolving stub. - FirstRunSetup.css is now the journey's shared design system (step rails, log panel, banners, hints, wizard chrome, check rows appended). - 2 new strings (Installing / Activity) translated across all 21 locales. Validated end-to-end on this machine: setup screen → Start installation → real venv bootstrap (~10 min) → backend healthy on 3900 → model wizard. 20/20 Rust tests · vite build · CJK guard green · installing act verified via mocked-IPC screenshot at stage=installing_deps. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(setup): --setup re-entry flag + make the install-plan screen un-stealable The setup stage is first-run-only by design (completed installs skip it), but it must be reachable on demand and must actually win the mount when engaged. Three fixes: - 'omnivoice-studio --setup' parks the bootstrap in AwaitingSetup on any launch — checked before the attach-to-healthy-backend shortcut, so a running backend can't skip past it - App routing: awaiting_setup now outranks everything (a live backend answering /setup/status used to route straight to the model wizard); the wizard additionally requires stage === 'ready' so it can't mount during the initial stage race - useBootstrapStage: a transient IPC miss no longer permanently declares 'ready' (which killed the poll loop and silently skipped the setup / progress screens) — it retries up to 5 ticks before conceding Plus journey-wide titlebar clearance (content never sits under the GTK headerbar / macOS traffic lights / Windows controls) and drag-region mastheads on all three acts. Verified: mocked-IPC harness with stage=awaiting_setup + a LIVE backend answering /setup/status renders the setup screen, not the wizard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style(setup): remove backdrop decoration — flat surface, state-only emphasis The corner accent glows and SVG film grain rendered as visible banding / noise artifacts on many panels — both gone; the journey now sits on a clean flat chrome background. Also swept the remaining decorative bloom: the active option card drops its glow shadow (flat accent tint + LED carry the state), and the armed Start button loses its pulsing halo (the lit LED already signals actionable). Remaining shadows are functional micro-detail only: 6px LED glows, meter track inset, red edge bars. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(setup): journey rail + verbosity diet — clean, smooth, elegant The setup page is now visibly stage 1 of the install flow: a quiet breadcrumb rail (SETUP → INSTALLING → MODELS & ENGINES) sits between the waveform and the headline on both the setup and installing acts, LEDs marking done/active/pending — one continuous story across the journey. Verbosity halved without hiding information: - option descriptions unfold (260ms ease) only on the selected card; the page shows exactly one explanation per group, collapsed cards keep the text as a tooltip - storage rows drop their always-on caption (label + path + readout + Change… on one line; caption lives in the row tooltip) The whole page now fits a laptop window without scrolling. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(setup): merge Models + Engines into one wizard act Two tabs weren't necessary: models are the required gate, engines the optional extras — now two stacked panels in a single 'Models & engines' step (label reuses the journey-rail key, translated in 21 locales). Wizard shrinks to 4 steps: Welcome → System check → Models & engines → Dictation. Continue still gates on models_ready only; engines stay optional. Welcome cards updated to the 3 remaining acts; static cards keep their descriptions visible (the active-only fold is for radios). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(setup): wizard was skipped after first-run install — probe /setup/status on bootstrap ready The models-needed probe started at mount with a ~30s retry ceiling. On a first run, mount happens at the setup page — by the time the user reads it and the multi-minute install finishes, the attempts were long burned, so setupChecked landed as 'no wizard needed' and the studio rendered with zero models on disk. The probe is now keyed on bootstrapStage and runs when it hits 'ready' — the first moment a backend exists to answer. Normal launches (backend up quickly) behave exactly as before. Caught by running the full journey three times end-to-end: rounds 2–3 skipped Models & engines after install; with the fix the wizard mounts with models_ready=false (Whisper large-v3 listed missing). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(setup): drop the Welcome step — wizard opens on System check The welcome act had nothing left to say: the journey rail names the stages, the setup page already oriented the user, and the cards repeated both. The wizard is now three steps — System check (auto-runs on mount) → Models & engines → Try dictation — landing the user directly on live preflight results instead of a page about the pages to come. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(setup): true unified library — models + engines as ONE list 'Merge them' meant one list, not two panels stacked — fair criticism. The wizard's Models & engines act is now a purpose-built WizardLibrary: every installable is a row of the same grammar (LED · name · chip · size · action): - required models lead (REQUIRED chip, Download action, live SSE progress bar + percent, green LED when installed) — they gate continue - TTS engines follow (ENGINE chip): active engine glows accent, available ones offer one-click Use (selectEngine), heavy installs defer honestly to Settings ('install later in Settings' + reason tooltip) - the optional-model tail folds behind 'Show N optional models' The full management surface (search, HF token, deletes, sorting) stays in Settings — a first run needs a checklist, not a store. 9 new strings × 21 locales. Verified against the live backend via the browser harness: required/installed/engine/active/Use/defer states all render in one list. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(diagnostics): local-first self-check, error journal, and bug-report pipeline (#296) * feat(diagnostics): local self-check + scrubbed bug-report pipeline Closes the gap between 'something broke' and 'a useful GitHub issue exists' — entirely within the local-first constraint: the only outbound path remains the user's own browser opening a prefilled issues/new URL. Backend: - core/scrub.py: privacy scrubber for anything leaving the machine — env-var secret values (*TOKEN*|*KEY*|*SECRET*|*PASSWORD*), credential shapes (hf_/ghp_/github_pat_/sk-), home dirs on all three OSes - core/diagnose.py: 9-check self-check (device+GPU, ffmpeg, HF token, disk, data-dir writability, RAM, engine registry, hub reachability), pre-scrubbed, ASCII-safe output - GET /system/diagnose + 'python main.py --diagnose' (exit 0/1) - /system/info: hardware inventory (os_version, cpu_model, cpu_count, ram_total_gb, gpu_name, vram_total_gb, disk_free_gb), cached statics Frontend: - utils/bugReport.js: single source for the prefilled-URL builder — scrubText twin, hardware context capture, scrubbed error+stack embed, URL-length cap; ReportBugButton refactored onto it - ErrorBoundary 'Report this bug' action with the error attached - utils/errorToast.jsx toastErrorWithReport(); wired into export toasts - Settings > About 'Run self-check' with per-check status badges Tests: 27 pytest (scrub, diagnose) + 15 vitest (bugReport); existing suites green; verified live (--diagnose, TestClient, vite build). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(diagnostics): error journal, diagnostic bundle, crash notice, global handlers Second slice of the bug-tracking work — still zero outbound paths beyond the user's own browser/file manager. - core/error_journal.py: deduped ring of recent unhandled backend errors (fingerprint counts, error_class triage: GPU_OOM, HF_AUTH_FAILED, PYANNOTE_LICENSE_REQUIRED, DISK_FULL, FFMPEG_MISSING, NETWORK_ERROR), scrubbed, JSONL-persisted so the error that killed the last run survives restart. Wired into the global exception handler; 500 bodies now carry error_class; GET /system/errors/recent. - core/diagnostic_bundle.py + POST /system/diagnostic-bundle + Settings > About 'Save diagnostic bundle': zip of self-check report, error journal, scrubbed log tails — drag onto a GitHub issue; bypasses the ~8k prefill-URL ceiling. - crash-on-next-launch: /system/notifications flags a crash logged before this session started (size vs acked-size in prefs, mtime vs process start); POST /system/crash/ack; LogsFooter acks on action click. - utils/globalErrorHandlers.js: uncaught errors + unhandled rejections get a throttled, noise-filtered 'Report this bug' toast. - sidecar log parity fix: _tauri_log_candidates() now lists the Rust sidecar's backend.log/backend_err.log on Linux (XDG state dir) and Windows (LOCALAPPDATA) — sidecar crashes were only visible on macOS. Tests: +19 pytest (journal, bundle); suite at 102 passed. Vitest 124 passed; vite build green. Live-verified: journal recorded and classified a real HF 401 from the test run (HF_AUTH_FAILED, paths scrubbed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(diagnostics): breadcrumbs, deep self-check, report sweep, issue search Final slice of the bug-tracking work. - toastErrorWithReport adopted at the high-traffic failure sites: TTS generation, dub upload/ingest/transcribe, engine install, engines-matrix load, voice profile save/delete/test, batch enqueue/cancel/delete. Validation toasts and cancellations stay plain on purpose. - utils/breadcrumbs.js: local-only ring of the last 20 action names (closed-set names only — never content or paths), embedded as a 'Recent actions' section in the prefilled report. Instrumented: view changes, generate, dub pipeline, export, engine switch. - deep self-check: /system/diagnose?deep=true and --diagnose --deep load the active engine and synthesize a short utterance (num_step=4) — catches 'installed but broken'. 180s time-box, skips during model load, scrubbed failure detail. Verified live: cold-loaded omnivoice and produced 2.2s of audio in 43.9s on CUDA. - 'Search similar issues' action on the ErrorBoundary: scrubbed, noise-stripped GitHub issue search URL — dedupe before filing. - bug_report.md template now points at the diagnostic bundle and the --diagnose CLI so manual reports arrive with the same evidence. Tests: pytest 107 passed (4 new deep-check tests, CJK gate green); vitest 218 passed (breadcrumbs + issue-search suites); vite build green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(diagnostics): self-diagnosis section in troubleshooting + README pointer Settings > About self-check / --diagnose / --deep / diagnostic bundle are now the documented first step before the per-error entries — and the support team's first ask on every issue. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(setup): flush sticky action bar, global dbl-click maximize, open maximized First-run polish on the studio-console journey: - FirstRunSetup: fixed-footer / scrollable-middle layout — mast + decision grid live in a dedicated .frs__scroll region; the install action bar is the last flex item, so it sits flush at the window's bottom edge and nothing (e.g. an expanded compute-option description) can render beneath it on small windows. - Double-click-to-maximize on the custom borderless titlebar now works on EVERY drag region (splash, first-run, wizard, main header) via one delegated listener in main.jsx, on all platforms; removed App.jsx's redundant inline handler so it doesn't double-toggle. Skips interactive controls in the bar. - Window opens maximized to the available desktop size (tauri.conf.json). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(diagnostics): quiet Bandit on the journal hash and hub probe The journal fingerprint is a dedup key, not a security boundary — usedforsecurity=False. The hub reachability probe gets an explicit https scheme guard on its constant URL so the urlopen sink is audited. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(setup): address PR #295 review findings — security, lifecycle, privacy, i18n Security: - setup.rs valid_mirror: reject plaintext http:// mirror URLs (MITM supply-chain path into UV_PYTHON_INSTALL_MIRROR / UV_INDEX_URL / HF_ENDPOINT); explicit http://localhost / 127.0.0.1 / [::1] exceptions only. Tests extended incl. loopback-lookalike hosts. - setup.rs detect_hardware: AMD vendor ID alone no longer maps to kind="rocm" — a cheap ROCm userspace probe (/opt/rocm or rocminfo on PATH) gates it; bare AMD GPUs report kind="amd" so the UI offers ROCm without pre-selecting it ("matches this machine" only when verified). Functional: - lib.rs/setup.rs --setup re-entry: complete_setup now kills any backend still serving on the port before retry_bootstrap, so changed env/mirror/layout settings actually apply instead of re-attaching. - setup.rs: nvidia-smi probe runs behind a 3 s timeout thread — a wedged driver degrades to CPU instead of hanging the first-run IPC. - setup.rs: is_first_run is now a pure read; the existing-install migration write moved to migrate_existing_install_if_needed, invoked only from the bootstrap thread (get_setup_state no longer writes). - setup.rs complete_setup: config save errors now abort setup and surface in the UI instead of bootstrapping into a stale on-disk layout. - setup.rs complete_setup: logs default-vs-custom flags instead of the user's absolute env/data/models paths (privacy rule). - scrub.py + bugReport.js: also redact forward-slash Windows homes (C:/Users/<name>, file:///C:/Users/...), ordered before the macOS pattern so "C:~" residue can't form. Tests added on both sides. - bugReport.js: context fetches bounded by a 2.5 s AbortController timeout so report assembly degrades to partial context instead of hanging on a stalled backend. - system.py: crash ack is now {size, mtime} (legacy size-only ack still honored) and /system/logs/clear drops the ack — truncation can no longer permanently suppress 'crash-last-session'. - system.py: Linux Tauri-log probe honors XDG_DATA_HOME. - setup.ts/WizardLibrary.jsx: SetupProgressEvent type now documents the full phase taxonomy actually emitted (per-file start/progress/done + install_*/delete_* lifecycle); reducer verified correct against the backend stream and annotated — a file-level 'done' must not clear the repo row. - SetupWizard.jsx: step rail clamps to the highest unlocked step (preflight/models gates) — no more jumping straight to "Enter studio". Polish: - BootstrapSplash.jsx: Waveform heights wrapped in useMemo([bars]) like its siblings. - BootstrapSplash.jsx: detectHints returns i18n keys (bootstrap.hint_*) rendered through t(); translated in all 21 locales. - SetupWizard.jsx: step rail aria-label localized (setup.step_aria / setup.step_completed) in all 21 locales. - FirstRunSetup.css: deprecated word-break: break-word → overflow-wrap: anywhere; reduced-motion override also stops the frs-hw-pulse LEDs (.frs-step.is-active LED + .swiz-lib__led--busy). Deferred (design-level, follow-up PR): --setup re-entry round-tripping of custom dirs/mirrors into the form (setup.rs), and worker-thread leak on timed-out deep checks (diagnose.py). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(i18n): translate back-filled keys in all 20 locales, drop inline fallbacks The reconciliation merge back-filled 16 new keys (about.self_check*, about.*bundle*, dub.num_speakers_*, errors.*) with English text in every non-English locale — CodeRabbit flagged 9 locales; fixed all 20. Interpolation tokens preserved and asserted during the rewrite. Also removed the two inline English fallback strings in App.jsx (firstrun.first_sound_*) so copy lives only in locales/*.json. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: mergetest <test@local> |
||
|
|
7419986c8b |
fix(dictation): microphone permission — OS usage descriptions, WebView grant handler, actionable denied-state UI (#323) (#336)
On Windows 11 the dictation pill (Ctrl+Shift+Space) always reported "Microphone access denied" even though OS-level mic permission was granted (Voice Clone worked, backend transcribed fine). Root cause: no WebView2 PermissionRequested handler was registered, so WebView2 fell back to its own permission UI — which the 300x64 transparent, undecorated, deliberately-unfocused pill window can never host — and getUserMedia() rejected with NotAllowedError. Per-platform fixes: - Windows (WebView2): register a PermissionRequested handler on both the main and widget webviews that allows microphone/camera requests in code, for the app's own origin only (tauri.localhost + dev loopback). The Windows privacy toggle still applies on top. - Linux (WebKitGTK): the media-stream enable + permission auto-grant previously covered only the "main" window — the dictation widget is a separate WebView and was silently denied. Now applied to both. - macOS: already correct — NSMicrophoneUsageDescription ships in src-tauri/Info.plist and wry grants media capture to the app origin; documented in the shared helper. Frontend: getUserMedia failures are now mapped by error name (utils/micError.js) instead of one blanket "access denied" toast — permission denials get a per-OS "where to re-enable it" hint (Windows hint now mentions the desktop-apps mic toggle), missing devices and busy devices get their own messages, and the previously hardcoded English toast in useRecording goes through i18n. New keys added to all 21 locales. Tests: vitest unit tests for the error mapping (19 cases) and a Rust unit test for the WebView2 origin allow-list; Windows handler code cross-checked against webview2-com 0.38.2 / windows-core 0.61.2. Fixes #323 Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
ea26893bfc |
fix(scripts): desktop-prod works from cmd/PowerShell via cross-platform launcher (#282) (#333)
`bun run desktop-prod` (and its :run/:upgrade/:pill/:run:pill variants) invoked `bash scripts/desktop-prod.sh` directly. On Windows, cmd and PowerShell have no `bash` on PATH unless Git Bash happens to be there, so the documented from-source install path died with a cryptic spawn failure before printing anything — the exact first step in issue #282's repro. Add scripts/desktop-prod.mjs, a tiny launcher (runs under bun or node): - macOS/Linux: execs the bash script unchanged — zero behavior change. - Windows: locates Git Bash via `where.exe bash`, well-known Git for Windows install paths, or derived from git.exe's location; explicitly skips C:\Windows\System32\bash.exe (the WSL launcher, which would run the script inside Linux and wipe/launch the wrong paths). - No usable bash: prints an actionable error (install Git for Windows, use `bun run desktop`, or use the installer) instead of a spawn error. All flags are forwarded untouched and the child's exit code is propagated. scripts/desktop-prod.sh itself is unchanged, and docs/install/windows.md now lists Git for Windows as a prerequisite for from-source installs. Refs #282 Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
bd60559e3a |
chore(probe): standardized PR-report publisher with redaction + review gate (#334)
Turns the ad-hoc 'attach a probe trace to the PR' habit into one script: redacts credentials/home-dirs/emails/IPs from the HTML report, prints a markdown digest, prunes old local reports, and only uploads (secret gist + PR comment) behind an explicit --post --yes after human browser review. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
78f5db71d7 |
fix(updater): preview channel offers the newest build across channels (#326) (#335)
Root cause, two layers: 1. tauri-plugin-updater's default comparator is plain semver (remote > current). Preview builds are published as X.Y.Z-N (e.g. 0.3.5-41 = main, 41 builds after the 0.3.5 tag), which semver treats as a *pre-release* of X.Y.Z — so it sorts BELOW stable X.Y.Z. Once stable 0.3.5 shipped, preview users were told "you already have the latest version" forever. 2. The endpoint list [preview, stable] is not a "best of both" — the plugin stops at the first manifest that parses and uses later endpoints only as network fallbacks, so a reachable preview manifest hid a newer stable release entirely. Fix: for the preview channel, check BOTH manifests with a custom version_comparator implementing cross-channel ordering (higher base version wins; on equal base a suffixed preview build outranks the bare stable it was built on; preview-vs-preview uses numeric-aware semver pre-release comparison), then offer the newest candidate. A manifest error is non-fatal while the other manifest answers. The stable channel keeps the single endpoint and the plugin's default comparison — default behavior unchanged on all platforms. Adds 7 unit tests covering preview ahead of stable (the bug case), stable passing preview, equal-base both directions, equal versions (no ping-pong), numeric build-counter ordering, and base dominance. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
2ef42ee629 |
feat(design): free-text 'describe your voice' field maps to design parameters (#317) (#331)
Parity with the hosted omnivoice.app describe field, implemented fully locally: a deterministic, ordered synonym-table mapper (no model, no network, stdlib only) projects a natural-language description onto the existing six-category design space (Gender/Age/Pitch/Style/EnglishAccent/ ChineseDialect). Every emitted token is validated at import time against the engine taxonomy, so the mapper can never produce an instruct item the engine validator would reject; Chinese token forms are derived from the taxonomy, never hardcoded (the one functional pinyin->dialect mapping is allowlisted in test_no_hardcoded_cjk.py with justification). UI: a describe textarea in the Design tab fills the attribute picker live (hand-tuning still possible afterwards); parts of the description the taxonomy can't express are listed back to the user as 'ignored' instead of failing silently. New i18n keys in all 21 locales. Fixes #317 Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
48ae4dae1d |
fix(dub): re-dub honors transcript edits — fingerprints canonicalised, preview cache-busted, atomic mux (#281) (#329)
* fix(dub): re-dub honors transcript edits — fingerprints canonicalised, preview cache-busted, mux made atomic (#281) Three symptoms, three causes: 1. Edited line, unchanged result: the dubbed preview-video URL was identical across re-dubs, so the WebView kept serving the previous dub. A generation nonce now cache-busts the preview after every completed generation. 2. Preview stuck loading forever: overlapping preview requests ran ffmpeg against the same output path and the mtime cache check saw the half-written file as valid. The mux now runs under a per-path lock, writes to a temp file, and os.replace()s into place. 3. One edit re-dubs all lines: server-side fingerprints were computed from pydantic-parsed segments (defaults filled in) but recomputed client-side from raw dicts (keys omitted), so every segment always looked stale and incremental degraded to a full re-dub. Values are now canonicalised on the backend and the frontend builds generation inputs through one shared helper (utils/segments.js) for both the generate request and the incremental plan. Fixes #281 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Potential fix for pull request finding 'CodeQL / Uncontrolled data used in path expression' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * fix(dub): realpath containment for job-derived preview paths (CodeQL) Request-supplied job_id/lang flowed into the preview mux output path. Both now pass a realpath containment guard against DUB_DIR (the file's existing per-segment pattern) and lang is allowlist-validated before it lands in a filename. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(dub): inline the containment guard — CodeQL can't track it through a helper Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> |
||
|
|
433f1ba617 |
fix(tts): /generate honors the selected TTS engine (#312) (#324)
* fix(tts): /generate honors the selected TTS engine (#312) The /generate route always ran the OmniVoice model directly, ignoring both the Settings engine selection and any per-request override. It now resolves the active backend (env var > Settings selection > default), supports an explicit `engine` form field (same pattern as /ws/tts and /v1/audio/speech), reuses the per-process engine instance cache, keeps inline [pause Nms] markers working on every engine, and honors applies_own_mastering so studio engines skip the broadcast mastering chain. The OmniVoice default path is byte-identical to the old behavior — existing API consumers see no change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(312): resolve modules at run time, drop lifespan client — fixes full-suite isolation tests/backend/** runs before tests/test_*.py and pollutes sys.modules (re-imports the services tree), so module-level imports bound at pytest collection pointed at a stale services.tts_backend — registry patches landed on a dict the routes no longer read ('Unknown TTS engine' in CI). Modules are now resolved through sys.modules inside each test. The client fixture also drops the module-scoped lifespan context manager that bound event_bus queues to this module's loop (teardown 'Queue bound to a different event loop') — plain function-scoped TestClient, the test_api.py pattern. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e8f1f5e057 |
fix(bootstrap): self-heal structurally broken venv instead of exiting 106 (#314) (#325)
A venv with no pyvenv.cfg (interrupted creation, half-deleted dir, or a managed Python that was removed) made the backend exit 106 forever; the only fix was manually deleting .venv. Bootstrap now (1) validates venv structure before declaring it ready and (2) recognizes the broken-venv death signature (exit 106 / 'No pyvenv.cfg file') after spawn — in both cases it quarantines only the .venv itself (rename-aside if deletion fails, never user data) and rebuilds through the normal setup path with existing progress stages. Healing is attempted once per launch; a healthy venv is never touched. The spawn+health-poll loop is extracted from lib.rs and shared with the retry path. Fixes #314 Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
13a3794358 |
fix(design): stop button + single-playback manager for voice previews (#316) (#322)
Voice previews and synthesized outputs could overlap with no way to stop them: playBlobAudio() fire-and-forgot a fresh Audio()/AudioContext per call, and each component (Design demo grid, gallery, demo player) kept its own uncoordinated audio handle. - Add utils/playback.js: a global single-playback manager. claimPlayback() stops whatever was playing before registering the new playback, returns a release() for natural end, and exposes stopActivePlayback() plus a usePlaybackSource() hook for UI affordances. - Register every preview/output path with the manager: playBlobAudio (Synthesize output, profile previews, dub segment previews), DemoPresetGrid cards, VoiceGallery previews (archetypes / community / imports), and the CloneDesignTab "Hear demo" player. - Visible stop affordance: while a synthesized output is playing, the Design/Clone footer CTA becomes a "Stop playback" button (new i18n key clone.stop_playback in all 21 locales). Preview cards keep their existing play/pause toggle, now wired through the manager. - Tests: unit suite for the playback manager (claim/stop/release/ subscribe semantics) and two DemoPresetGrid regression tests for the single-playback invariant and the stop toggle. Fixes #316 Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
226aeaa81a |
style(icons): thinner HD icon strokes app-wide + themed native file inputs (#300)
Lucide ships stroke-width 2 on a 24px grid; at the app's 11-16px render sizes that weight reads heavy. One global rule (svg.lucide) re-weights every icon to 1.5 with geometricPrecision shape-rendering — crisper, lighter, no call-site churn. Hand-rolled SVGs (logo mark, batch spinner) don't carry the .lucide class and keep their bespoke weights; the one explicit per-icon strokeWidth (archetype icons) is dropped so the global weight governs everywhere. Native <input type="file"> chips are now themed via ::file-selector-button mirroring .ui-btn--subtle (chrome tokens, pill radius, hover states). All current file inputs hide behind themed labels, but any visible one — future panels, the LAN/share web view — no longer renders the OS-default grey button. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
9cc55ef75e |
feat(setup): flush action bar, global dbl-click maximize, open maximized (#318)
- First-run action bar is now a pinned flex sibling below a dedicated scroll region (.frs__scroll) — flush to the window's bottom edge, with nothing rendering beneath it; only the content above scrolls. - Double-click-to-maximize is wired once in main.jsx, delegated across every data-tauri-drag-region (splash, first-run, wizard, main header) on all platforms, skipping interactive controls. Replaces the wizard-only handler in App.jsx. - Main window opens maximized (tauri.conf.json). - Setup wizard preflight checks flow into responsive columns on wide windows instead of one tall single column. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
bfc90e90f5 |
fix(bootstrap): sync venv deps on app upgrade — stale venv crashed on new imports (#307) (#319)
Upgraded installs replaced backend/ + omnivoice/ sources from the bundle but never refreshed pyproject.toml/uv.lock or re-ran uv sync, so any dependency added after the user's venv was created was missing at import time — e.g. a venv predating scalar-fastapi (added May 4) died on startup with ModuleNotFoundError once v0.3.5 code landed on it. - bootstrap.rs: refresh pyproject.toml + uv.lock from the bundle whenever a healthy venv is reused; when the lockfile content changed, run `uv sync --frozen --no-dev` so newly added deps land. On sync failure (e.g. offline upgrade) keep the existing venv instead of bricking a previously-working install. - bootstrap.rs: the repair path now refreshes manifests first (it used to sync against the stale lock from when the venv was created) and applies the restricted-network HTTP env tuning it was missing. - backend/main.py: scalar_fastapi import is now guarded — it only powers /docs, so a venv without it must still boot; /docs returns 503 with an actionable message instead. Closes #307 Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9312e434ef |
fix(asr): clone references transcribe via the ASR registry, not the broken transformers pipeline (#308) (#321)
Voice cloning without a transcript fell through to OmniVoice's built-in load_asr_model() — a transformers pipeline() load of whisper-large-v3-turbo that fails outright on transformers 5.3 — even when whisperx / faster-whisper / mlx-whisper were installed and working. The dub pipeline already used the registry; the /generate clone path never did. - services/asr_backend.py: new transcribe_reference() resolves the active registry backend (honoring auto-detect order and the OMNIVOICE_ASR_BACKEND override), extracts text from either result shape (top-level "text" or whisperx-style segments), and degrades to None on any failure so the model fallback behaves exactly as before. When the registry itself resolves to pytorch-whisper it defers to the model's lazy load instead of building a second pipeline. - api/routers/generation.py: transcript-less references get transcribed in the GPU pool before inference. - tests/test_transcribe_reference.py: covers both result shapes, failure degradation, and the pytorch-whisper deferral. The remaining half of #308 — pytorch-whisper itself being incompatible with transformers 5.3 when it truly is the last resort — is tracked in the issue. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d04c1fdd0d |
fix(dub): Timing strategy options never rendered — wrong prop name on Segmented (#313) (#320)
The Timing control passed `options=` to <Segmented>, whose prop is `items=` (defaulting to []), so the toggle group rendered as a single empty pill with nothing to click — users had no way to pick Concise / Stretch Video / Strict slot. Broken since the control was introduced; every other Segmented call site already uses `items=`. Closes #313 Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
f3e403193e |
fix(dictation): macOS auto-paste — don't steal focus, write clipboard natively (#287) (#299)
Dictation via the global shortcut transcribed fine but the text never reached the target app on macOS, due to two stacked bugs (diagnosed, patched, and verified by @geektf in #287): 1. The ShortcutState::Pressed handler called win.set_focus(), making the widget frontmost — the simulated ⌘V from simulate_paste() landed in the widget instead of the app being dictated into. Skip set_focus() on macOS (same #[cfg(not(target_os = "macos"))] guard the other widget call sites already use). 2. With the widget unfocused, the WebView clipboard APIs (navigator.clipboard.writeText / execCommand('copy')) fail silently in WKWebView, so ⌘V pasted whatever was previously on the clipboard. simulate_paste now takes Option<String> and writes the transcript to the clipboard natively (arboard) before sending the keystroke — no window focus required. CaptureWidget passes the transcript; copyText() stays as best-effort for browser (non-Tauri) mode, and the optional param keeps any text-less call sites working. cargo check clean (the unreachable_code warning in setup.rs is pre-existing from #286); frontend node:test suite passes. End-to-end behavior verified by the reporter on macOS 26 / M4 Pro with both patches applied. Fixes #287 Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
71cdc1553e |
fix(dub): video retry after URL ingest, responsive layout, icon-only toolbar (#304)
Three reported issues in the dubbing editor: 1. Dark video after YouTube ingest: the preview mounted while yt-dlp was still finalizing the media file — the first load failed (MediaError 2 network / 4 non-media body) and the once-only error handler declared the source dead, leaving a black box until the project was reloaded. The error handler now retries with backoff (up to 6× over ~21s) before giving up; decode errors (3) stay terminal. 2. Responsive/resizable layout: min-width:0 on the split-grid columns (the classic shrink trap), settings-bar fields get real shrink room instead of locked min-widths, bulk selects flex, prep-bar overlays are viewport-bounded, and the segment table's fixed rails narrow at 1100px and collapse speaker/gain entirely below 760px so the text column keeps usable width at any size. 3. Toolbar: Save / Reset / Export are icon-only with hover tooltips (+ aria-labels); Generate Dub keeps its label as the primary verb. Skeleton header matches. Vitest 196/196 green. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0bb026f6f8 |
feat(setup): optional Hugging Face token in the library act (#303)
The unified library dropped the inline HF-token field the old ModelStoreTab embed used to provide — so onboarding produced installs with no token, and users hit the 'speaker diarization disabled' wall on their first multi-speaker dub. Restored as a quiet disclosure at the bottom of the Models & engines act: password input → POST /system/set-env HF_TOKEN (same durable persistence Settings uses), saved/error states, Enter-to-save. Copy names the concrete benefit (pyannote diarization) and the local-first promise (token stays on this machine). 6 strings × 21 locales. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
e424f46656 |
test(setup): update DictationDemo asset-missing contract to #294 (#302)
The test asserted the component renders nothing when demo clips 404 — the exact behavior #294 deliberately removed (it blanked the wizard's Try-dictation act on every real install). New contract under test: the script cards are asset-gated and disappear; the hotkey card (shortcut + press-to-verify, zero assets needed) stays. This was the single failure breaking CI on main since #294 merged (34 files / 196 tests green with the fix). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
1171185c9d |
style(setup): stable scaffold — no layout shift anywhere in the journey (#301)
Fair criticism taken: vertically centering variable-height content meant every act and step reflowed the page around its own center, and selecting an option pushed everything below it. The journey now has one stable scaffold — only the content region changes: - deck is top-anchored (waveform opens the page right under the titlebar; the centering dead-zone is gone) and fills the viewport - footer (serial plate, totals, armed action) is sticky at the bottom with a soft fade — never scrolls out of view, hugs the bottom when content is short - variable text gets reserved space: masthead subtitles hold two lines; option descriptions move out of the cards into a fixed two-line caption slot per radio group (aria-live), so switching options swaps text in place with zero shift — cards themselves are title-only - description tooltips retained on every card Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c6a7c84f24 |
feat(setup): first-sound ending, a11y pass, orphan-backend EPIPE fix (#298)
* feat(setup): first-sound ending + accessibility pass
First sound — onboarding ends with the product doing the thing: the
moment the studio mounts after the wizard, one short line is generated
locally and played ('Welcome to your studio. Every word you hear was
generated on this machine, just now.' — localized, 21 locales), with a
toast naming what just happened. sessionStorage handoff so it fires only
on the run that completed the wizard; every failure path is silent — a
first impression must never surface an error.
Accessibility:
- WAI-ARIA radio pattern on all option groups: roving tabindex (selected
option owns the tab stop) + Arrow-key navigation, selection follows
focus; groups get aria-labels
- aria-live='polite' on the installing act's stage label so screen
readers hear stage transitions
- contrast: quiet text raised from 0.45–0.55 to 0.6–0.68 opacity — small
visual change, real WCAG gain
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(backend): orphaned backend couldn't load models — EPIPE-safe stdio
Caught in the wild by the in-app diagnostic report: when the desktop
shell that spawned the backend dies but the backend survives, its
stdout/stderr pipes close — and transformers' tqdm weight-loading bar
crashes the entire model load with BrokenPipeError on the next write.
Fix: wrap sys.stdout/stderr in utils.hf_progress.SafeFileWrapper (the
same EPIPE-swallowing wrapper the patched hub tqdm already uses) at
startup. Logs are best-effort for a server process; model loading is
not. Progress bars stay alive — they feed the loading-progress UI via
hf_progress listeners, so disabling them was not an option.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
27ce2b2da2 |
feat(setup): onboarding quick wins — trust line, resume reassurance, download ETAs (#297)
Three small high-leverage additions from the onboarding audit: - trust line on the setup page footer — 'Everything runs and stays on this machine — no account, no cloud, no telemetry.' The product's thesis, stated at the moment the user decides. - resume reassurance on the installing act and (while downloading) in the model library — 'Interrupted downloads resume automatically — closing the app is safe.' Kills unnecessary Clean&Retry panic; uv and the HF hub both genuinely resume. - ETAs on the long waits: the installing act derives an EMA byte-rate from successive bootstrap-progress events; library rows aggregate the per-file rates already on the SSE stream. Shown as '~3m left', only while a total is known and progress is mid-flight. 3 new strings × 21 locales. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
118ca3b237 |
fix(setup): Try-dictation act was blank — keep the hotkey card when demo clips aren't bundled (#294)
The wizard's final act rendered nothing on installs without the
build_demos.sh sample WAVs (they aren't committed or shipped — every
real install hits this). DictationDemo returned null whenever the asset
probe 404'd, hiding the hotkey card too, even though that card teaches
real things with zero assets: the registered shortcut and live
press-to-verify via the tray-dictate events.
Now only the replayable script cards gate on the bundled WAVs; the
hotkey card always renders, with a hotkey-only lede ('hold, speak,
release — press it now to verify') translated across 21 locales.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
88614d215d |
i18n(license): translate AGPL commercial-license strings across 20 locales (#292)
Follow-up to the AGPL relicense (
|
||
|
|
e49a0163ea |
fix(release): MSI-legal preview version stamp — numeric pre-release identifier (#293)
The Windows preview build dies in WiX with 'optional pre-release identifier in app version must be numeric-only and cannot be greater than 65535 for msi target' because the stamp was BASE-preview.N. Drop the word: BASE-N is still a valid semver prerelease (sorts below the stable BASE for the updater channel), unique per run, and MSI-legal. Failed run: 27096586578 (Windows x64; macOS + Linux built fine but the publish job was skipped). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
43884e54a6 |
feat: first-run setup screen — install mode, storage choice + minimum-space gate, mirrors, compute (#286)
* feat(setup): first-run install gate — nothing installs until the user confirms a plan New `setup` module parks first runs in BootstrapStage::AwaitingSetup instead of auto-installing. complete_setup validates the user's InstallPlan and only then starts the existing bootstrap: - install modes: installed (platform dirs) / portable (one folder next to the exe / AppImage, config.json travels with it) - user-chosen storage: env dir, data dir (OMNIVOICE_DATA_DIR), model cache (OMNIVOICE_CACHE_DIR) — None = legacy default, byte-identical behavior - minimum-space gate: per-volume free-space check (fs4 statvfs), grouped by filesystem so dirs sharing a disk sum their requirements; install refused when short (9 GiB env + 7 GiB models + 1 GiB data, measured + headroom) - custom mirrors (PyPI index, HF endpoint, python-build-standalone) take precedence over region presets in the venv/sync/backend env wiring - ROCm torch variant selectable via config (env var still wins) - existing installs migrate silently: venv present → setup_complete=true, no questions re-asked; dev trees skip the gate entirely 19 unit tests (disk probing, space grouping, mirror validation, legacy config compat). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(setup): first-run setup screen — mode, storage with space gate, mirrors, compute FirstRunSetup renders when the Rust side reports awaiting_setup (lazy-loaded; regular launches pay nothing). One screen, defaults all work: - language picker first (rest re-renders translated), 21 locales shipped - Installed / Portable mode cards (portable disabled with reason when the exe-adjacent folder isn't writable) - storage rows with live per-path free-space probes (debounced check_install_target), 'needs ~X / Y free' readouts, folder pickers - client mirrors the Rust per-volume space gate: Start installation is disabled with an explicit reason until every volume fits - compute (CUDA-auto / ROCm), update channel, region + custom mirror URLs - complete_setup errors surface inline; on success the normal bootstrap progress UI takes over on the next status poll Verified on a wiped machine: gate parks (no spawn, no downloads), screen renders, 450 GB ≥ 17 GB requirement → Start enabled. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(setup): studio-console redesign of the first-run screen The setup screen now reads as powering on studio hardware rather than a web form — true to a voice studio, and self-sufficient offline (every font and asset is bundled; a first run may be on a restricted network): - breathing waveform masthead (CSS-only, deterministic speech-cadence silhouette, staggered per-bar delays) - Source Serif 4 display headline + engraved IBM Plex Mono panel labels + Inter body — the three faces the app already ships - rack-unit panels with corner screws, engraved title rules, serial plate (OVS · vX.Y.Z) - disk space as segmented LED capacity meters: lit = what the install consumes, alarm-blink red on insufficient volumes - mode cards with indicator LEDs; 'armed' Start button — LED lights and a halo pulses only once every volume passes the space gate - atmosphere: corner accent glows + SVG film grain; staggered rise-in choreography on load - all motion transform/opacity only; prefers-reduced-motion holds every frame still; theme-token derived colors; focus-visible rings throughout No logic changes: same IPC calls, same i18n keys, same space-gate math. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(setup): wide desktop deck, hardware-aware Compute + Update channel cards Three pieces of feedback addressed: - width: the console is now a 1240px two-column deck (storage rail left, decision rail right) that uses desktop real estate; collapses to one column under 980px and stacks fully under 620px - no outer chassis box: panels float directly on the atmospheric backdrop, each carrying its own rack-unit treatment - Compute and Update channel split into separate cards with real information: get_setup_state now detects hardware (nvidia-smi → CUDA name, /sys/class/drm vendor 0x1002 → AMD/ROCm, Apple Silicon → MPS, CPU cores + RAM via sysinfo; best-effort, never blocks) — the Compute card shows a live 'Detected: …' readout, badges the option that matches the machine, and pre-selects ROCm on AMD boxes; both cards use LED radio options with full descriptions (6 new i18n keys × 21 locales) Also pins playwright-core as an explicit devDep — bun did not materialize it through @playwright/test, breaking programmatic browser use. 20/20 Rust tests · vite build · CJK guard green. Verified live (gate engaged, responsive single-column) and at 1600×1000 via mocked-IPC browser shot (two-column deck). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(setup): move network (region + mirrors) into the masthead with language Language and download region are the two 'where am I' choices — they now sit together top-right of the masthead, with the custom-mirrors disclosure tucked beneath the subtitle. The Network panel is gone, leaving a balanced deck: Install mode + Storage left, Compute + Update channel right. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(setup): strip the boxes — fills and rules carry the structure One design rule now: borders only where state demands them. Panels lose their boxes entirely (engraved mono title + rule separates sections); option cards, storage rows, selects/inputs, the hw readout, the version plate and the ghost buttons are all flat fills; active options glow with an accent tint + LED; blocked rows and errors use a red tint + 2px inset edge bar instead of a border. The badge chip is fill-only too. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(setup): quiet pass — every element earns its visual weight - waveform becomes a whisper: 22px trace, 2px bars, ~half opacity — an ambient signature instead of a billboard - storage readouts collapse to one mono line ('needs ~9 GB · 449 GB free'); the LED meter now appears only when it carries information (install would consume >35% of free space, or the volume is blocked) — at 449 GB free a bar was a meaningless sliver - Change… buttons go text-quiet (transparent until hover) - custom-mirrors disclosure right-aligns under the region select it extends, instead of floating under the subtitle - version plate moves to the footer next to the disk total — the masthead keeps only title, subtitle, and the two locale/region selects Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(setup): platform-matrix awareness — distro+arch detection, ROCm gated to Linux, no Windows console flash The install matrix is OS family × distro × arch × GPU vendor, and the setup screen now both shows it and only offers choices valid for it: - HardwareInfo gains os_name (distro PRETTY_NAME from /etc/os-release on Linux, macOS/Windows elsewhere) and arch (x86_64/aarch64) — the detected line reads 'CachyOS x86_64 · NVIDIA RTX 4070 · 32×CPU · 31 GB RAM', exactly what bug reports cite - SetupState gains os; the ROCm option renders on Linux only (wheels don't exist elsewhere) and complete_setup clamps rocm→auto on non-Linux as the server-side backstop - nvidia-smi probe gets CREATE_NO_WINDOW on Windows — no cmd flash on the first screen a user ever sees - Apple Silicon → MPS, Intel mac → CPU, ARM Linux → CPU: all matrix cells resolve through the same base constructor Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(setup): unify the whole first-run journey under the studio-console system Setup → Installing → Model wizard now read as one continuous experience: the same atmosphere, whisper waveform masthead, serif/mono type, LED language and quiet fills across all three acts. - Installing (BootstrapSplash): rebuilt in frs-* — segmented LED journey meter (completed steps + live byte progress), LED step rail (done=green, active=pulsing accent, pending=dim), engraved ACTIVITY panel with the quiet mono log (collapse/copy as text-quiet actions), failure act with red-tint error + hints + armed Retry. All logic untouched: stage poll, event subscription + backfill, dedupe, hints, region/language selects. - Model wizard (SetupWizard): same masthead with the step rail as engraved mono LED steps top-right, welcome cards as option-card surfaces, preflight as LED check rows (pass/warn/fail), frs nav buttons with armed primaries, embedded Model Store / Engines / Dictation panels scroll inside the act. Old 556-line stylesheet replaced by ~60 lines of glue; BootstrapSplash.css reduced to a resolving stub. - FirstRunSetup.css is now the journey's shared design system (step rails, log panel, banners, hints, wizard chrome, check rows appended). - 2 new strings (Installing / Activity) translated across all 21 locales. Validated end-to-end on this machine: setup screen → Start installation → real venv bootstrap (~10 min) → backend healthy on 3900 → model wizard. 20/20 Rust tests · vite build · CJK guard green · installing act verified via mocked-IPC screenshot at stage=installing_deps. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(setup): --setup re-entry flag + make the install-plan screen un-stealable The setup stage is first-run-only by design (completed installs skip it), but it must be reachable on demand and must actually win the mount when engaged. Three fixes: - 'omnivoice-studio --setup' parks the bootstrap in AwaitingSetup on any launch — checked before the attach-to-healthy-backend shortcut, so a running backend can't skip past it - App routing: awaiting_setup now outranks everything (a live backend answering /setup/status used to route straight to the model wizard); the wizard additionally requires stage === 'ready' so it can't mount during the initial stage race - useBootstrapStage: a transient IPC miss no longer permanently declares 'ready' (which killed the poll loop and silently skipped the setup / progress screens) — it retries up to 5 ticks before conceding Plus journey-wide titlebar clearance (content never sits under the GTK headerbar / macOS traffic lights / Windows controls) and drag-region mastheads on all three acts. Verified: mocked-IPC harness with stage=awaiting_setup + a LIVE backend answering /setup/status renders the setup screen, not the wizard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style(setup): remove backdrop decoration — flat surface, state-only emphasis The corner accent glows and SVG film grain rendered as visible banding / noise artifacts on many panels — both gone; the journey now sits on a clean flat chrome background. Also swept the remaining decorative bloom: the active option card drops its glow shadow (flat accent tint + LED carry the state), and the armed Start button loses its pulsing halo (the lit LED already signals actionable). Remaining shadows are functional micro-detail only: 6px LED glows, meter track inset, red edge bars. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(setup): journey rail + verbosity diet — clean, smooth, elegant The setup page is now visibly stage 1 of the install flow: a quiet breadcrumb rail (SETUP → INSTALLING → MODELS & ENGINES) sits between the waveform and the headline on both the setup and installing acts, LEDs marking done/active/pending — one continuous story across the journey. Verbosity halved without hiding information: - option descriptions unfold (260ms ease) only on the selected card; the page shows exactly one explanation per group, collapsed cards keep the text as a tooltip - storage rows drop their always-on caption (label + path + readout + Change… on one line; caption lives in the row tooltip) The whole page now fits a laptop window without scrolling. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(setup): merge Models + Engines into one wizard act Two tabs weren't necessary: models are the required gate, engines the optional extras — now two stacked panels in a single 'Models & engines' step (label reuses the journey-rail key, translated in 21 locales). Wizard shrinks to 4 steps: Welcome → System check → Models & engines → Dictation. Continue still gates on models_ready only; engines stay optional. Welcome cards updated to the 3 remaining acts; static cards keep their descriptions visible (the active-only fold is for radios). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(setup): wizard was skipped after first-run install — probe /setup/status on bootstrap ready The models-needed probe started at mount with a ~30s retry ceiling. On a first run, mount happens at the setup page — by the time the user reads it and the multi-minute install finishes, the attempts were long burned, so setupChecked landed as 'no wizard needed' and the studio rendered with zero models on disk. The probe is now keyed on bootstrapStage and runs when it hits 'ready' — the first moment a backend exists to answer. Normal launches (backend up quickly) behave exactly as before. Caught by running the full journey three times end-to-end: rounds 2–3 skipped Models & engines after install; with the fix the wizard mounts with models_ready=false (Whisper large-v3 listed missing). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(setup): drop the Welcome step — wizard opens on System check The welcome act had nothing left to say: the journey rail names the stages, the setup page already oriented the user, and the cards repeated both. The wizard is now three steps — System check (auto-runs on mount) → Models & engines → Try dictation — landing the user directly on live preflight results instead of a page about the pages to come. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(setup): true unified library — models + engines as ONE list 'Merge them' meant one list, not two panels stacked — fair criticism. The wizard's Models & engines act is now a purpose-built WizardLibrary: every installable is a row of the same grammar (LED · name · chip · size · action): - required models lead (REQUIRED chip, Download action, live SSE progress bar + percent, green LED when installed) — they gate continue - TTS engines follow (ENGINE chip): active engine glows accent, available ones offer one-click Use (selectEngine), heavy installs defer honestly to Settings ('install later in Settings' + reason tooltip) - the optional-model tail folds behind 'Show N optional models' The full management surface (search, HF token, deletes, sorting) stays in Settings — a first run needs a checklist, not a store. 9 new strings × 21 locales. Verified against the live backend via the browser harness: required/installed/engine/active/Use/defer states all render in one list. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
abae6e290a |
chore(license): relicense from FSL-1.1-ALv2 to AGPL-3.0 (open-core)
Replace the Functional Source License with the GNU Affero General Public License v3 across the project, with a paid commercial license retained for proprietary/closed-source use without AGPL obligations (open-core, like Firecrawl). - LICENSE: verbatim AGPL-3.0 text under an AGPL Notice + Scope header; drops the FSL "Competing Use" framing and the 2-year Apache-2.0 conversion. The bundled omnivoice/ TTS model stays Apache-2.0 upstream (AGPL-compatible). - Manifests now declare SPDX AGPL-3.0-only: pyproject.toml, Cargo.toml (normalized from bare AGPL-3.0), and both package.json (added license field). - README.md / README_CN.md: badge, pricing, commercial-use FAQ, License section. - en.json: in-app Commercial License copy reworded to AGPL; the false "converts to Apache 2.0" FAQ removed (+ its renderer block in SupportPage.jsx). Non-English locale strings still describe the old FSL model and are left for a follow-up translation pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c5ba10b20a |
feat(tts): inline [pause Nms] marker for silence in generated speech (#276) (#277)
Lets users insert pauses in the transcript: `[pause]` (350ms default), `[pause 500ms]`, `[pause 1s]`, `[pause 1.5s]`. Requester confirmed the `[pause Nms]` syntax (fits the existing marker style). Implementation is fully opt-in and model-free: - `omnivoice/utils/text.parse_pause_markers()` splits the text into `(span, pause_ms_after)` tuples (case-insensitive; bare number = ms; `s` suffix = seconds; adjacent markers sum; clamped to 10s). Text with no marker returns unchanged, so existing behavior is untouched. - `_run_inference` synthesizes each span as today and stitches a `torch.zeros` silence buffer between them at the `[pause]` points (matching channel dims/dtype/device); DSP/mastering then runs once over the combined audio. An explicit overall `duration` isn't split across spans (left to the model per span). Tests (no TTS model loaded): tests/test_pause_markers.py covers the parser (ms/s/default/clamp/leading/trailing/adjacent/round-trip) and the silence stitching with a fake gen fn (lengths + zeroed regions). Full pause + CJK guard + router smoke suites pass (39). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c427ffa62d |
feat(dub): optional speaker-count hint for diarization (#274) (#275)
When a clip has multiple speakers, pyannote's auto-detect sometimes collapses
them into a single "Speaker 1" — so the transcript merges turns and the dub
mixes voices. The diarization-consumption side is correct (overlap-weighted,
distinct Speaker N ids — pinned by a new test), so the collapse comes from
auto-detect itself.
Add an optional speaker-count hint (the reporter's own suggestion):
- backend: `/dub/transcribe-stream/{job_id}?num_speakers=N` (clamped 1–20;
None → auto-detect) threaded to `diar_pipe(audio, num_speakers=N)`. Omitted
entirely when unset so we don't depend on the kwarg in every pyannote build.
- frontend: `dubNumSpeakers` store field + a compact "Speakers" number input
in the dub panel (placeholder "Auto") + i18n; `transcribeStreamUrl` appends
the param; the SSE hook reads the hint at stream-open time.
Tests: tests/test_assign_speakers_from_diarization.py (multi-speaker split,
overlap weighting, label robustness, empty-result safety) +
dub.transcribeUrl.test.ts (param appended only for a positive int). Full
backend diarization + frontend suites pass; CJK i18n guard passes.
Does NOT close #274 — pending the reporter confirming that setting the count
resolves the collapse on their video (can't verify pyannote behaviour without
a CUDA box + the clip).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
5fbc654e82 |
chore(release): v0.3.5 (#272)
Patch release. Version bumped across all sources + lock files; [0.3.5] CHANGELOG. Ships: - #270 — speaker diarization fixed on PyTorch >=2.6 (weights_only=True rejected the pyannote checkpoint's TorchVersion global); the loader now registers the shared safe-globals allowlist before loading. Tagging v0.3.5 triggers release.yml (desktop) + docker.yml. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f7d34a1433 |
fix(diarization): register torch safe-globals before pyannote load (#270) (#271)
On torch>=2.6, `Pipeline.from_pretrained("pyannote/speaker-diarization-3.1")`
fails with "Weights only load failed ... Unsupported global: GLOBAL
torch.torch_version.TorchVersion" — PyTorch 2.6 flipped torch.load's default to
weights_only=True and its secure unpickler rejects the checkpoint's metadata
globals. This broke diarization on torch>=2.6 even when the license IS accepted
(reported on v0.3.4, RTX 4070 Ti, license accepted).
The WhisperX VAD load already solved this via
`WhisperXBackend._allow_vad_pickle_globals()` (allowlists TorchVersion,
omegaconf nodes, pyannote metadata, builtins, numpy, …). `get_diarization_pipeline`
just never called it. Reuse it before the diarization load — idempotent,
per-process, verified to register TorchVersion on torch 2.8.
Graceful fallback (silence-gap heuristic) is preserved if anything still fails.
Tests: tests/test_diarization_weights_only.py (allowlist runs before load;
no-token short-circuit). Existing diarization classification tests still pass.
Cross-platform (the torch 2.6 weights_only change affects all platforms).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
fa64dcaa92 |
chore(release): v0.3.4 (#269)
Patch release. Version bumped across all sources + lock files; [0.3.4] CHANGELOG. Ships: - #255 — PyTorch-Whisper backend works as a standalone fallback (no cuDNN 8, no OMNIVOICE_PRELOAD_TTS_ASR=1), unblocking Windows+NVIDIA users hitting the cudnn_ops_infer64_8.dll error. Tagging v0.3.4 triggers release.yml (desktop) + docker.yml. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
63a0d00f09 |
fix(asr): PyTorch-Whisper fallback works without cuDNN 8 or preload (#255) (#268)
Windows + NVIDIA users hit `Could not locate cudnn_ops_infer64_8.dll`: WhisperX/faster-whisper run on CTranslate2, which needs cuDNN 8, but PyTorch 2.8 ships cuDNN 9 and the side-loaded `cudnn8_compat` libs were missing from the venv. The PyTorch-Whisper backend should have been the fallback, but it errored "set OMNIVOICE_PRELOAD_TTS_ASR=1" because it only worked when the TTS model preloaded an ASR head. - `PyTorchWhisperBackend._ensure_pipe()` now builds its OWN transformers ASR pipeline on demand (PyTorch stack → cuDNN 9, no CTranslate2/cuDNN-8), without loading the full TTS model and without the preload env var. A constructor- passed pipe (when the TTS model already has one) is still reused. Model is overridable via OMNIVOICE_PYTORCH_ASR_MODEL. - dub_core transcribe preflight no longer hard-rejects pytorch-whisper when no pipe is preloaded — it lazy-loads; any failure surfaces per-chunk with the real cause. So a Windows box without cuDNN 8 can switch ASR backend to "PyTorch Whisper" in Settings → Models and transcription works. Docs: troubleshooting entry. Tests: tests/test_pytorch_whisper_fallback.py (lazy standalone build, reuse of a passed pipe, no get_model() call, env override). Full tests/ suite: 700 pass. Does NOT close #255 — pending the reporter confirming the fallback works on their machine; the cuDNN-8 install gap (faster-whisper path) is a follow-up. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
4e65774610 |
chore(release): v0.3.3 (#267)
Patch release. Bumps version across all sources + lock files; adds [0.3.3] CHANGELOG. Ships: - #262 — Settings → About now shows the server's CPU architecture (was the client browser's platform, e.g. "Win32", in Docker). - Validates the bash-3.2 checksum CI fix on a real release (the macOS SHA256SUMS should now upload automatically). Tagging v0.3.3 triggers release.yml (desktop) + docker.yml (GHCR :0.3.3/:0.3/:latest). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
e740786a08 |
fix(about): show server CPU arch, not the client browser's platform (#262) (#266)
Settings → About → Architecture rendered `navigator.platform` — the *client browser's* OS. In the Docker/web build that's the remote machine (e.g. "Win32" when browsing from Windows), not the container, which is misleading. Expose the server's `platform.machine()` as `arch` on /system/info and render that instead, so the row reflects the machine OmniVoice actually runs on — for both the desktop app (local backend) and Docker. Note: the *blank* version/GPU/RAM/VRAM in the same report were the loopback-gate 403s fixed in v0.3.2 (#261); this PR fixes the remaining architecture row. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
35b62ad0c0 |
fix(ci): make SHA-256 checksum step bash-3.2 safe (macOS runner) (#265)
The "Compute SHA-256 checksums" step used `mapfile -t` (a bash 4+ builtin) but
macOS GitHub runners execute `shell: bash` as /bin/bash 3.2, which has no
`mapfile`. The step exited 127 ("mapfile: command not found") on the macOS leg,
so `SHA256SUMS-macOS Apple Silicon.txt` was never produced/uploaded for v0.3.1
and v0.3.2 (the binaries themselves shipped fine; only the macOS checksum file
was missing and had to be regenerated by hand each time).
Replace `mapfile` with a portable `while IFS= read -r … done < <(find … | sort)`
loop (works on bash 3.2). Verified on bash 3.2.57: builds the array correctly,
handles spaces in bundle filenames. Linux/Windows legs are unaffected.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
27b4e151e1 |
chore(release): v0.3.2 (#264)
Patch release. Bumps the version across all sources + lock files and adds the [0.3.2] CHANGELOG section. Ships: - #261 — "Loopback origin required" 403s in the Docker admin UI (and blank version): the image now runs in OMNIVOICE_SERVER_MODE so the loopback gate is relaxed for the headless deployment; desktop loopback boundary unchanged. Tagging v0.3.2 triggers release.yml (desktop) and docker.yml (GHCR :0.3.2/:0.3/:latest). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b4f1fe18d7 |
fix(server): relax loopback gate in headless server mode so Docker admin UI works (#261) (#263)
In Docker the loopback origin gate (`require_loopback`) is unenforceable: Docker's NAT rewrites `request.client.host` to the bridge gateway (e.g. 172.17.0.1) even for a localhost-only `-p 127.0.0.1:3900:3900` mapping, so every request looks non-loopback. The gate then 403s the operator out of the routes the web UI needs — `/system/*` (incl. `/system/info`, which left the version blank, re-breaking #249 in Docker) and `/api/settings/*` (HF-token entry) — surfacing as "Loopback origin required" all over the UI. Fix: add an explicit, opt-in `OMNIVOICE_SERVER_MODE` flag. When set, `require_loopback` becomes a no-op; exposure is then governed by the operator's port mapping plus the optional share PIN (NetworkAccessMiddleware still 401s unauthenticated non-loopback clients whenever a PIN is set). The Docker image sets `OMNIVOICE_SERVER_MODE=1` (Dockerfile + documented in compose). Security: the desktop build NEVER sets this, so its loopback boundary is unchanged — LAN share guests are still denied the admin/system routes. New unit tests lock the contract (strict 403 by default incl. the PR #81 vectors; relaxed only under the flag). Existing non-loopback 403 tests still pass. Docs: docker.md troubleshooting entry for "Loopback origin required". Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
cec15070a5 |
chore(release): v0.3.1 (#260)
First tagged build of the 0.3 line off main. Bumps the version across all sources (pyproject + frontend package.json + Tauri conf + Cargo + both lock files + the source-checkout fallback in core/version.py) and adds the [0.3.1] CHANGELOG section. Ships: - #256 — browser/Docker file-export crash (invoke undefined) - #249 — version surfaced in web/Docker UI + desktop-only updater hidden - #255 — transcribe stream now surfaces the real ASR/model-load failure Tagging v0.3.1 triggers release.yml (desktop binaries) and docker.yml (GHCR :0.3.1 / :0.3 / :latest). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
2aa6e3502b |
fix(dub): surface real ASR/model-load failures instead of dropping the stream (#255) (#259)
* fix(dub): surface real ASR/model-load failures instead of dropping the stream (#255) When the transcribe SSE stream died before emitting any event, the UI showed a misleading generic "Transcribe stream dropped before emitting any segments. Likely ASR backend failed to load" — hiding the real cause (e.g. a faster- whisper/CTranslate2 cuDNN load failure, or a missing pkg_resources). The per-chunk transcribe was already wrapped, but two preflight/setup calls in the stream generator were not — if either raised, the connection dropped with no structured error event: - `get_model()` (preflight) — now wrapped; failures emit a structured `error` event built via `core.failure.build_failure` (sanitized reason + actionable hint, e.g. the pkg_resources→setuptools hint). - `offload_tts_for_asr()` — now non-fatal; an offload hiccup logs and continues rather than killing the stream. - The empty-segments guard now sanitizes each chunk error (no home-path/token leakage) and appends the recognized-failure-class hint. Adds a regression test: a raising `get_model()` must yield a structured `error` SSE event carrying the real message, not a dropped connection. Does NOT close #255 — this makes the underlying cause visible (pending the reporter's backend log) rather than asserting a specific Windows-CUDA fix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(dub): drive transcribe-stream gen directly (avoid cross-loop teardown) The regression test for #255 hit the SSE streaming endpoint through TestClient, whose lifespan created an asyncio Queue bound to a different event loop than the streaming request — erroring at teardown in the full-suite run. Drive the route's async generator directly instead: the preflight-error path yields a single event with no executor/Queue, so it stays isolated from any app loop. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
621c263354 |
fix(version): surface running version in web/Docker UI + hide desktop-only updater (#249) (#258)
The Docker web build has no Tauri runtime, so Settings → About → Version read `getVersion()` (Tauri-only) and rendered a dash — leaving Docker users unable to tell which version they were running (issue #249). The update-channel toggle was also shown there even though the auto-updater is desktop-only. - Backend: expose the single-source `APP_VERSION` over HTTP — add it to `/system/info` (`app_version`) and `/health` (`version`). Both are model-free and the latter is zero-auth. - Frontend: Settings → About → Version falls back to `info.app_version` when no Tauri `getVersion()` is available, so Docker shows the real 0.3.x version. - Frontend: hide the update-channel toggle, update-endpoint row, and the "Check for updates" button outside Tauri — the Docker image updates by pulling a new tag, not via the in-app updater. - Docs: fix the wrong package name in the version-check command (`omnivoice-studio` → `omnivoice`) and document the new `/health` version field + the in-UI version row. Tests: assert `/system/info.app_version` and `/health.version` equal APP_VERSION (test_router_smoke.py). The stale `:latest` tag itself was already fixed in #252; cutting a v0.3.x release repopulates it. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
e2a33e8c40 |
fix(frontend): browser/Docker fallback for file export (closes #256) (#257)
The history-item export button (and the dub/audio export path) called the
Tauri `save` dialog unconditionally. In the Docker web-server build there is
no Tauri shell, so the plugin's internal invoke() dereferences an undefined
__TAURI_INTERNALS__ and crashes with:
TypeError: Cannot read properties of undefined (reading 'invoke')
…which is exactly what users hit when downloading a freshly cloned voice from
the browser/Docker UI.
Fix: extract a shared `browserDownload` helper (utils/download.js) that does a
plain HTTP-blob download via a temporary <a download>, and guard
`handleNativeExport` on `isTauri` — falling back to that helper (streaming the
file already served at /audio/<path>) when no Tauri runtime is present.
`triggerDownload`'s browser branch now reuses the same helper instead of
duplicating the blob-download logic.
Adds utils/download.test.js covering the Content-Disposition parser and the
no-Tauri download path (regression guard for #256).
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
f555fe0720 |
fix(bootstrap): surface setuptools-repair failures + verify pkg_resources (follow-up to #253) (#254)
* fix(bootstrap): surface setuptools-repair failures + verify pkg_resources (follow-up to #253) Three gaps flagged by review bots on PR #253 are addressed: 1. **Layer-2 repair result captured** (`bootstrap.rs` ~line 481): the `let _ = run_streaming(...)` that silently discarded network/permission failures from the targeted `uv pip install setuptools>=75,<80` is replaced with a `match` block that logs `log::info!` on success and `log::error!` on failure (consistent with the Layer-3 path). 2. **Post-repair re-verification added** (`bootstrap.rs`): after the targeted install in Layer 2, a second `import pkg_resources` check is run. If pkg_resources is *still* absent, a `log::error!` with an actionable remediation message is emitted before returning. This closes the gap where bootstrap handed back a known-bad venv that caused the dubbing crash (#248) with no clear signal in the log. 3. **Test strengthened** (`bootstrap.rs` `setuptools_repair_uses_correct_specifier`): the test now mirrors the exact `&[&str]` slice used in both repair branches and asserts `repair_args[2] == "setuptools>=75,<80"` as a single positional argument. This catches the split-arg regression the review bot identified (e.g. `["setuptools>=75", ",<80"]`) which would silently install the latest setuptools and leave pkg_resources absent. 4. **Smoke-test INST-01/02 hardened** (`scripts/smoke-test.sh`): exports `UV_PYTHON_PREFERENCE=only-system`, `UV_HTTP_TIMEOUT=120`, and `UV_HTTP_RETRIES=5` before the `uv run` import checks so that failures reflect real bootstrap regressions, not harness-network timeouts. `cargo test bootstrap` → 5 passed, 0 failed. Closes review findings on #253. Related: #248. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(bootstrap): fail clearly when pkg_resources repair fails (PR #254 review) - ensure_venv_ready now returns None (via fail()) when pkg_resources is still missing after the targeted setuptools repair, instead of returning a venv that crashes on the first ASR/dub call. The 'pkg_resources' message routes to the PKG_RESOURCES_MISSING failure mapping for a clear, doc-linked remediation. - smoke-test.sh: correct the comment (timeout+retry vars, not a non-existent index var). --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ef98aae4db |
test(probe): whole-app coverage — dubbing, i18n, engines, security, migration, dictation, design (#247)
* test(probe): expand coverage — dubbing, i18n, engines, security, migration, dictation, design, coverage-critic Broadens the probe harness from one happy-path spec per layer to whole-app feature coverage (web, backend, dictation, clone, design), keeping the Actor/Judge split and offline-by-default + enable-on-demand for heavy paths. New specs + judges (one subprocess boot shared across backend-touching specs): - dubbing (L4): segment duration-ratio, SRT/VTT well-formed, export-archive contents, output language-ID (advisory) - i18n: locale files valid JSON (gate); orphan-keys + coverage (advisory). NOTE: surfaced a real bug — all 20 non-en locales carry gallery.cat_*/ bootstrap.lines keys absent from the en reference (reported, not gated). - engine matrix: active engine available + every unavailable engine explains why (11 TTS / 7 ASR backends via /engines/*) - loopback security: system routes reject non-loopback origins (403) - DB migration: alembic UPGRADE on the seeded omnivoice_data fixture - Coverage Critic: every declared layer still has a spec (gate) + API inventory - dictation: streaming-ASR WebSocket /ws/transcribe registered + handshake - voice design: reuses the audio-correctness ladder - real ASR round-trip: enable-on-demand (PROBE_E2E=1) Enriched _boot_runner.py to capture engines/asr/loopback/openapi/ws in ONE isolated boot (conftest boot_capture session fixture); added env.seeded_data_dir. 13 specs total. probe suite 74 passed / 5 skipped; full repo 687 passed, 0 failures. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(probe): address all 15 unresolved review findings on #247 - coverage.py:22 — use `with open(...)` context to close spec files after yaml.safe_load (file handle leak) - _boot_runner.py:80 — store only `type(exc).__name__` for WS errors; drop raw str(exc) that could leak home paths / secrets into capture JSON - _boot_runner.py:99 — snapshot DB files before boot; set db_created=True only when boot creates NEW files (not when fixture already had one) - dubbing.py:46 — FAIL segments_duration_ratio when validated==0 (guards against empty/corrupt segment list passing vacuously) - i18n.py:49 — FAIL locale_valid_json when locales_dir is empty/missing - i18n.py:7 — fix docstring: locale_no_orphan_keys is advisory, not blocking - test_probe_i18n.py:59 — assert r.passed is False, not just r.advisory - coverage_critic.probe.yaml:15 — add "meta" to required layers list - dub_export.probe.yaml:17 — capture dub_audio in steps before advisory reads it - migration.probe.yaml:13 — add path_exists(db_path) data-integrity check - test_probe_asr_e2e.py:33 — os.path.exists → os.path.isfile for PROBE_ASR_SAMPLE - test_probe_migration.py:24 — assert context["db_path"] (presence) not db_created (new creation), aligning with the boot_runner fix Two findings intentionally skipped with reasons (see review thread replies): test_probe_design.py:36 — offline pattern is intentional; actor step is bypassed by design throughout the probe suite for CI compatibility test_probe_engines.py:22 — whisperx pin is intentional; it verifies the shipped default ASR engine is available out-of-the-box Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(probe): ASCII x in dubbing detail (ruff) + run migration judges inside seeded dir Two regressions from the hardening pass: - dubbing.py: replace non-ASCII '×' with 'x' (Ruff ambiguous-unicode → Tests lint fail) - test_probe_migration: move run_judges inside the seeded_data_dir with-block so the new path_exists check sees the DB before the temp dir is torn down (was always failing) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ab492c0e8e |
fix(docker): fix stale :latest tag + add push-to-main trigger (#252)
* fix(docker): fix stale :latest tag and add push-to-main trigger (closes #251, addresses #249) Two bugs caused the Docker image to be stale (showing v0.2.7 inside a :latest/:0.3.x-tagged image): 1. **`:latest` was never set on tag pushes.** The metadata-action rule `type=raw,value=latest,enable={{is_default_branch}}` evaluates `is_default_branch` as false on tag-triggered runs (which run in a detached-HEAD context, not on the default branch). The tag rule was replaced with `enable=${{ github.ref_type == 'tag' }}` so `:latest` is updated on every `v*` tag push. 2. **No trigger for main-branch pushes.** There was no way to keep an up-to-date `:main` edge image between releases. Added `push: branches: [main]` which produces a `:main` rolling tag. Also added a note in the workflow and docs clarifying that the update-channel toggle (Settings → About) is a Tauri desktop feature and does not apply to the Docker image (headless web-server build). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(docker): gate mutable tags to push events + guard :latest against prereleases (PR #252 review) - semver / :latest / :main now require github.event_name == 'push' so a manual workflow_dispatch only ever emits a throwaway :sha- tag (no mutable-tag rollback) - :latest excludes prerelease tags (ref contains '-') so an rc/beta can't clobber it - header + tag-strategy comments corrected (:sha- emits on every trigger) Addresses greptile + coderabbit review on #252. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c98b0aaf6f |
fix(bootstrap): guarantee pkg_resources in backend venv (closes #248) (#253)
Root cause: the existing-venv fast-path in ensure_venv_ready() only
checks `import uvicorn` before returning — it never verified that
`pkg_resources` (dropped by setuptools≥80, issue #224) was present.
Users who installed before commit
|
||
|
|
24a00bea64 |
test(probe): spec-driven AI-agent test harness (L1–L5 + HTML report + triage) (#245)
* test(probe): add spec-driven AI-agent test harness (L1/L2/L4/L5 + report + triage) Introduces `tests/probe/`, a portable, mostly-deterministic test harness built on the Actor/Judge split: AI agents may drive and self-heal, but verdicts are always deterministic code + metrics — no LLM on the verdict path. Layers: - L1 API: Schemathesis property-fuzz over in-process ASGI (enable-on-demand). - L2 web: Playwright Driver + deterministic self-heal (id→test-id→text, loosened CSS) → pluggable Healer; LLMHealer/anthropic_healer for genuine agentic heal. Judges + self-heal logic unit-tested offline via FakePage; live browser skips. - L4 media: audio correctness — exists/decode/duration/not-silent/clipping/NaN, round-trip ASR WER (pure-python, faster-whisper backend), speaker similarity. No golden-WAV (device-stable metrics only); naturalness is advisory-only. - L5 env/first-run: fresh-data-dir backend boot in a SUBPROCESS (no session contamination), asserts health + DB init + endpoint reachability. Docker gated. Plus: hybrid YAML spec engine + JudgeResult/registry; self-contained HTML report that auto-opens (suppressed in CI/headless/PROBE_NO_OPEN); Triager that clusters failures and drafts a prefilled GitHub issue URL (sanitized, no auto-submit) with a one-click button in the report. Dependency-light: runs in the base venv; schemathesis/resemblyzer/playwright/ anthropic are enable-on-demand and skip cleanly. Generated reports gitignored. Full suite green (657 passed); no contamination of existing tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(probe): add L3 desktop layer (Tauri config-integrity + guarded launch) Per the architecture decision, desktop E2E is substituted by backend-over-HTTP (L5) + browser (L2) since Tauri has no official macOS WebDriver. L3 guards the packaging/shell contract a browser test can't see, against the real tauri.conf.json (with platform-override merge), running on any platform with no Tauri toolchain: - version parity between tauri.conf.json and pyproject (release integrity) - dev/build wiring (devUrl matches the Vite frontend, frontendDist, before* cmds) - bundled binaries first-run depends on (uv / ffmpeg / ffprobe in externalBin) - CSP actually permits the local backend origins (desktop-only failure mode: packaged app can't reach :3900 while the browser build works) Adds desktop.py (config load + platform deep-merge + bundle discovery + launch guard), judges/desktop.py (config_present/config_eq/config_contains/csp_allows), desktop_smoke.probe.yaml, and tests covering integrity, platform-merge replace semantics, and a live bundle launch that skips without a built bundle/display. Full suite green (662 passed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
208719a555 |
fix: dub OOM fallback, watermark on /generate, Settings responsiveness (#241 follow-up) (#244)
* fix(dub): whisperx CUDA OOM → CPU fallback instead of a bare 500 Found while exercising the dub pipeline on an 8 GB RTX 4070 Laptop GPU: with the TTS model + GPU worker pool resident, whisperx's CTranslate2 load of large-v3 dies with "CUDA failed with error out of memory", and POST /dub/transcribe surfaced it as an unhandled 500 with no guidance. WhisperXBackend now catches a CUDA OOM at load and retries on CPU (int8, same model + accuracy, just slower) after clearing the CUDA cache. Dubbing keeps working on small/laptop GPUs instead of dead-ending. Only triggers on a CUDA OOM, so the MPS/CPU paths are untouched (cross-platform parity). Verified: /dub/transcribe on the prepped job went 500 → 200 with correct segments. Added a deterministic unit test (forces the OOM, asserts the device switches cuda→cpu; a non-OOM RuntimeError still propagates). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(watermark): embed invisible watermark on /generate output, not just dubs embed_watermark was wired only into the dub pipeline (dub_generate.py), so plain TTS from /generate came out unmarked even with invisible watermarking enabled — i.e. the setting silently did nothing for the main generate path. Embed it on the final audio in the generate handler too. embed_watermark self-gates on the setting + AudioSeal availability and passes audio through unchanged on failure, so it's a no-op when off and never breaks generation. Verified: detector on a fresh /generate clip went is_watermarked:false → true, confidence 1.0, message OMNI ("OM"), is_omnivoice:true. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(settings): wrap the settings sub-nav so tabs don't clip out of view The settings sub-nav has 10 tabs (General…Privacy) but the shared .ui-tabs primitive is a non-wrapping inline-flex row, so on a narrow Settings pane the later tabs (Credentials/Logs/About/Privacy) overflowed the right edge and were unreachable. Scope flex-wrap to `.ui-tabs.settings-tabs-ui` only — the bar now grows to 2–3 rows instead of running off-screen. The shared primitive (used by the models role tabs, log-source tabs, etc.) is unchanged. Verified at 900px (2 rows) and 700px (3 rows): all 10 tabs visible. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(settings): connect active tab to content via accent + tighten spacing Make the active settings tab read as connected to the panel below: each tab carries its own semantic accent (already in TAB_DEFS — Models pink, Engines purple, …) instead of a uniform pink, and that accent is threaded down as --settings-accent to paint a matching hairline along the top of the content panel. The shared colour ties tab→content subtly and wrap-proof (no fragile positional connector). Content wrapped in .settings-content with deliberate margin/padding so it breathes under the bar; the bar's own bottom margin is dropped so the bridge owns that gap. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(engines): make the compatibility matrix responsive (scroll, don't overlap) On a narrow Settings pane the matrix's fixed-width columns (status/gpu/ isolation/actions ≈ 630px) plus the flexible name column couldn't fit, so the cells collapsed and OVERLAPPED — name text rendered under the AVAILABLE/ACTIVE badges and GPU chips. Give the table a horizontal-scroll container with a shared header/body min-width (840px) and stop the fixed cells from shrinking, so columns keep their shape and stay legible at any width (scroll for the overflow) — the same data-table treatment used elsewhere. Fills normally on wide panes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(settings): border-connect the active tab to its content panel Refine the tab→content connection from a single accent hairline to a "border-connect": the pill bar opens at its bottom (flat corners, no bottom border) into a 3-sided panel (.settings-content) framed in the active tab's accent, with a 2px full-accent top edge at the seam. The bar + panel read as one outlined container, and the active tab's colour visibly feeds into the panel it opens. Accent is threaded per-tab via --settings-accent. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
a94fc435a5 |
fix(asr): repoint two 404 ASR model repo IDs in catalog (closes #239) (#242)
* fix(asr): repoint two 404 ASR model repo IDs in catalog (closes #239) Model install failed with HTTP 404 for two ASR entries whose Hugging Face repos don't exist: - UsefulSensors/moonshine-small -> UsefulSensors/moonshine-tiny (Moonshine ships tiny/base; there is no 300M 'small') - Systran/faster-whisper-large-v3-turbo -> deepdml/faster-whisper-large-v3-turbo-ct2 (Systran publishes no turbo repo; deepdml is a valid CTranslate2 build) Audited all 25 catalog repo_ids — every one resolves 200 on HF after the swap. Adds a static (no-network, CI-safe) regression test asserting repo_ids are well-formed and the known-404 IDs can't reappear. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test/docs(asr): safer repo_id access + flag turbo repo as community build (PR #242 review) - test_known_404_repo_ids_absent: m.get('repo_id','') so a missing field gives a clean assertion instead of KeyError regardless of test order. - models.yaml: note the turbo entry is a community CTranslate2 conversion to re-verify on future audits (greptile). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5ad160a2e9 |
fix(license): declare actual FSL-1.1-ALv2 in pyproject (was Apache-2.0) (#236)
pyproject declared license = "Apache-2.0", but the repo's LICENSE is FSL-1.1-ALv2 (Functional Source License; each release converts to Apache-2.0 two years after publication). The Apache-2.0 declaration was inaccurate for the current grant. Declared as a PEP 639 LicenseRef since FSL isn't an OSI/SPDX-listed identifier. Validated: hatchling accepts the expression and builds the project cleanly (uv build OK), so uv sync / packaging in CI is unaffected. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
8690833137 |
feat(update): move update pill into status bar + Updates panel (changelog/channel/history) (#240)
* docs(spec): updates-in-status-bar design (move pill to LogsFooter + Updates panel) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(plan): updates-in-status-bar implementation plan (11 tasks, TDD) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(plan): pin i18n task to scripts/translate_all.py backfill * feat(update): pure chip + release presentation helpers * feat(update): listReleases + fetchAppVersion wrappers * feat(update): transient releasesSlice composed into store * feat(update): app version + channel in updaterSlice * feat(update): list_releases Tauri command (GitHub releases) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(update): UpdateStatusChip bar indicator * feat(update): UpdatesPanel (live row + channel + releases list) * feat(update): mount chip+panel in LogsFooter, retire floating UpdateBadge Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(update): Settings channel switcher shares store value (auto-sync) * i18n(update): add updates.* keys across 21 locales Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(deps): lock reqwest for list_releases command * polish(update): a11y radiogroup on channel switch, safer release key, drop dead test seam Addresses final-review nits (non-blocking): role=radiogroup/radio + aria-checked on the channel Segmented; key={r.name||r.version} to avoid collisions; remove the unused vi import + __loader seam in releasesSlice.test.ts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(update): i18n the channel-set error + correct flagged updates.* translations (PR #240 review) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(update): add 10s timeout to list_releases HTTP client (PR #240 review) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(update): guard chip Restart against in-flight dub job (greptile P1, PR #240) The always-visible status chip's one-click Restart (ready state) called installUpdate→relaunch without the dub-busy guard the panel uses, so a user with a dub/transcription job running could lose in-flight work. Mirror the panel's gate: toast update.busy and bail when dubStep === 'generating'. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(update): surface channel-switch failures in the Updates panel (greptile, PR #240) Mirror Settings' error handling: the panel's stable/preview switch now catches a failed set_update_channel and toasts settings.channel_set_failed instead of an unhandled rejection. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c857be816a |
fix(gallery): OmniVoice Gallery rename, dark dropdowns, and noisy/stale archetype previews (#241)
* fix(gallery): rename to "OmniVoice Gallery" + fix dark-theme dropdown colors The gallery heading now reads "OmniVoice Gallery" (gallery.title, all 21 locales — brand prefix on each localized word). The facet filter <select>s (Gender/Age/Pitch/Accent/Language) rendered with the OS-default light control surface on the dark theme: .facet-select set background/border from --bg-tertiary / --border-color, which are defined nowhere. An undefined var() reads as transparent on the sibling <div> filters (fine over the dark page) but falls back to the native light background on a form control. Switch to the defined dark-chrome tokens and add color-scheme: dark + an explicit dark option list so the popup matches across WebKit / WebView2 / WebKitGTK. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(gallery): archetype previews render a noise buzz instead of voice The Hype Host, The Podcaster and The Vlogger previews played a loud tonal buzz, not speech. The preview renderer pinned num_step=16 and seed=42; the "social" sample script at that exact point lands on a degenerate diffusion trajectory and collapses to a near-pure tone. The blank-audio guard missed it because the buzz is loud (peaks near -2 dBFS), not silent — so the garbage was cached and served. The cache key is (instruct, language) only, so it never self-corrected. - Bump preview num_step 16 -> 32: reliably converges to speech across the gallery's instruct/script space (one-time, cached render cost). - Add a spectral-flatness floor (_is_unusable_audio) so a degenerate tonal render is rejected like a blank one, reusing the existing retry-on-new-seed path. Whisper/breathy voices are broadband (high flatness) so they're safe. Verified: flatness Hype Host 0.001->0.050, Podcaster 0.0002->0.083, Vlogger 0.004->0.039; whisper control (Calm Guide) 0.239, not flagged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(gallery): stop preview playback replaying stale cached audio Preview audio is re-rendered server-side when an archetype is fixed, but the URL is stable and the response carried no Cache-Control — so the WebView's HTTP cache replayed the first clip it ever fetched (e.g. the old buzz) indefinitely, even after the server file was corrected. - Frontend: fetch previews with { cache: 'no-store' } so playback always pulls current bytes. - Backend: send Cache-Control: no-cache on the preview response so any client revalidates against the ETag instead of serving a stale clip. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(e2e): add Playwright UI smoke + gallery specs and preview-quality unit test UI testing system to catch regressions like "Use design → Importing a module script failed" (a dead Vite/module server) and the noisy-preview bug. - Playwright (frontend/e2e): drives the system chromium (no browser download) against the Vite dev server. ui-smoke mounts all 12 routable views and fails on any code-split/import failure, uncaught exception, or ErrorBoundary fallback. gallery.spec asserts the "OmniVoice Gallery" heading, the dark facet dropdowns (computed bg = rgba(255,255,255,0.04), not the OS-default light surface), and that opening an archetype in the Designer mounts the lazy CloneDesignTab. `bun run e2e`. - backend/tests/test_archetype_preview_quality.py: unit-tests the _spectral_flatness / _is_unusable_audio guard with synthetic signals (tone < threshold < speech < noise; loud tone + silence are unusable) and pins the render constants. CI-safe — no model/GPU. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
d58e5a9e38 |
feat(launchpad): Transcripts card + recent OmniDrive files strip (#235)
- New Transcripts action card (lime accent, FileText) → opens the Transcriptions view, alongside Clone/Design/Dub/Stories/Gallery. - Below the cards, a "Recent files" strip shows the last few exports from OmniDrive (GET /export/history, already loaded on mount) with a "View all files →" link that jumps to the full OmniDrive browser (Projects page). Hidden when there are no exports yet. App.jsx passes exportHistory to Launchpad; reuses existing lp-project-card chrome, adds a small files-head + view-all + grid in index.css. 5 new launchpad.* keys, backfilled across 21 locales. Verified: tsc clean, build OK, vitest 167/167, CJK guard passes. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
07479be04f |
feat(support): unify Donate + Commercial License behind one toggle (#234)
* feat(support): unify Donate + Commercial License behind one toggle The donate page and the commercial-license (enterprise) page were two separate full-screen modes reached from different places. Merge them into a single SupportPage with a charming segmented toggle: - Segmented "💛 Support ⇄ 🏢 Commercial License" control with a sliding active pill that carries each panel's accent hue (pink for Support, teal for License) and an icon that pops on selection. - Switching cross-fades the panel (key remount replays the hero/card entry animations) over the shared Launchpad aurora + a single Back button. - Both legacy modes still work: 'donate' opens the Support tab, 'enterprise' opens the Commercial License tab — so the footer heart and the dub/export "commercial license" links land on the right tab unchanged. Reuses the existing donate/enterprise chrome (DonatePage.css + EnterprisePage.css kept and imported); SupportPage.css only owns the toggle + transitions. Both views share one 640px container width so the frame doesn't jump on toggle. DonatePage.jsx + EnterprisePage.jsx removed (content folded in). 3 new support.* i18n keys, backfilled across 21 locales. Verified: tsc clean, build OK (SupportPage chunk replaces the two old ones), vitest 167/167, CJK guard passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(support): add 'Other ways to help' chips + FSL hero_note fix + i18n backfill - SupportPage: add Star GitHub / Join Discord chips below donation methods - SupportPage CSS: vertically center short Support panel, single-column donation grid, ghost-pill chip styles - Fix FSL hero_note wording across all 21 locales to accurately reflect the license (internal use at any scale is free; only competing product/service triggers commercial license) - New i18n keys: support.other_ways, support.star_github, support.join_discord * feat(support): polish Support panel + correct Commercial License wording Support panel (from screenshot feedback): - Donation methods now stack in a single clean column — no orphaned PayPal card floating in a half-empty second row. - Short Support panel is vertically centered so it no longer clings to the top of an empty page (License stays top-aligned; it's tall enough to fill). - New "Other ways to help" row: Star on GitHub + Join Discord ghost chips, so people who can't donate still have a real way to support — and it balances the layout. Commercial License wording: - Fixed enterprise.hero_note: it implied "deploying at scale (pay-per-use API)" triggers a commercial license. Per the actual FSL-1.1-ALv2, scale does NOT trigger licensing — internal use is free at any scale; the trigger is offering OmniVoice to others as a competing product/service. Reworded to say exactly that, and re-translated across all 21 locales. 3 new support.* keys. Verified: tsc clean, build OK, vitest 167/167, CJK guard passes, 21 locales at parity. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: drop stray stories-editor plan doc that slipped into the branch This planning artifact (with CJK i18n examples) was accidentally swept into an earlier commit on this branch; it isn't part of the Support-page feature and isn't on main. Removing it so the CJK guard passes — the committed tree no longer carries hardcoded CJK outside the translation layer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
dc453cf9da |
fix(i18n): close coverage gap the translation PR missed (#230 follow-up) (#232)
The "full i18n coverage" PR (#230) was based on a stale snapshot, so strings added to main after that point — the updater/channel dialog, dictation shortcut, and a batch of export/save/project toasts — were still hardcoded English. This extracts the remaining user-facing imperative strings (toasts + the update ask() dialog) plus the adjacent JSX labels in DubFailureNotice: - App.jsx (17): export/save/download/project/flush toasts → i18n.t('app.toast_*') (App already imports the configured i18n instance; reused it rather than plumbing a hook through 17 handlers). - Settings.jsx (14): save/clear failures, engine-switch, channel, updater download/install + the "Update available" ask() dialog, dictation-shortcut set/register/reset → t('settings.*'). - DubTab.jsx (DubFailureNotice): added the useTranslation hook; "Diagnostic copied"/"Copy failed" toasts + "Open docs"/"Copy diagnostic" labels. 38 new keys added to en.json, backfilled across all 21 locales. No regressions: the only shared-key value change from the #230 merge was the intentional engines.unavailable casing fix. Verified: 21 locales at parity, tsc clean, build OK, vitest 167/167, CJK guard. NOTE: this covers imperative strings (toast/ask) in these 3 files. A full codebase audit of all JSX text/placeholders is a larger separate sweep. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
dec67dc619 |
feat(gallery): calmer, more elegant archetype cards (#231)
The "Use voice" buttons were solid, fully-saturated per-category color fills — 16 loud, differently-hued blocks on screen drew the eye to the buttons instead of the voice names. Polish pass: - Use-voice button: tonal by default (13% accent wash + accent-colored text + hairline accent border), going solid only on hover/focus. Keeps the per-category hue as identity but lets the resting grid stay calm; the CTA lights up on the card you're pointing at. - Chip row: always rendered with a reserved min-height so cards without an accent/whisper chip (e.g. Captain Crusty) no longer leave a ragged void — action rows now line up across the grid. - Designer (wand) button: quiet at 0.5 opacity at rest, full on card hover/focus — it's tertiary, so it no longer competes on every tile. - Card hover border softened a touch. color-mix() is already used in 17 frontend files (proven on all WebView targets). Verified: tsc clean, build OK, vitest 167/167. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
40f96db1d7 |
feat(l10n): extract remaining 531 hardcoded strings — full i18n coverage (#230)
* feat(l10n): complete translations for all 21 languages (837 keys each)
Translate all UI keys across every component for 20 non-English locales:
ar, de, es, fr, hi, id, it, ja, ko, nl, pl, pt, ru, sv, th, tr, uk, vi, zh-CN, zh-TW
- 837 flattened keys per language (100% coverage)
- Covers settings, splash, main UI, dialogs, tooltips, errors
- Placeholders ({{var}}) and HTML tags (<1>) preserved
- Add translate_all.py batch script for future re-translations
* feat(l10n): extract remaining 531 hardcoded strings and translate all UI
Scan found ~250 hardcoded user-facing strings across ~30 component files.
Extracted all into en.json (837 → 1368 keys, 49 namespaces) and updated
every component to use t() / i18next.t().
Components updated (35 files):
- Zero-i18n: AudioTrimmer, CaptureWidget, CastingView, CheckpointBanner,
CompareModal, DirectionDialog, EngineCompatibilityMatrix, ErrorBoundary,
FloatingPill, KeyboardCheatsheet, NetworkToggle, ReadinessChecklist,
SupertonicLicenseDialog, VoicePreview, BatchAddDialog
- Partial-i18n: BootstrapSplash, DubSegmentRow, Header, LogsFooter,
DictationDemo, DubbingDemo, NavRail, MultiLangPicker, SearchableSelect,
Sidebar, Settings, SetupWizard, EnterprisePage, VoiceGallery,
SharingPanel, ReportBugButton, StoriesEditor, App.jsx
- Hooks: useDubWorkflow.js, useTTS.js (using i18next.t directly)
New namespaces: trimmer, casting, checkpoint, compare, direction, errors,
keyboard, header, sidebar, network, readiness, license, voicePreview,
models, enterprise_faq, gallery_extra, dub_workflow, tts_errors, sharing,
reportBug, app
All 20 non-English locales translated to 100% (1368 keys each).
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
f290575593 |
fix(logs): import useAppStore in LogsFooter (donate + notification nav crashed) (#229)
LogsFooter.jsx called useAppStore.getState().setMode(...) in four handlers
(donate button + notification action targets, lines ~405/447/880/922) but
never imported useAppStore — clicking any of them threw
'ReferenceError: Can't find variable: useAppStore' and the handler died.
Add the canonical 'import { useAppStore } from "../store"'. tsc + build clean.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
c2ce92cf69 |
feat(launchpad): surface Stories + Voice Gallery; fix gallery key-spread warning (#228)
- Launchpad showed only Clone/Design/Dub. Add ActionCards for the two newer
modes: Stories (multi-voice audiobooks → setMode('stories')) and Voice Gallery
(browse designed-voice archetypes → setMode('gallery')). i18n in en + zh-CN,
backfilled across all 21 locales.
- VoiceGallery: stop spreading a 'key' prop into <ArchetypeCard {...cardProps}>
(React dev warning + ignored). cardProps no longer carries key; pass key={a.id}
directly at the two render sites.
Verified: tsc clean, build OK, vitest green, CJK guard passes.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
94ed7e6347 |
feat(stories): redesign — chapter section bars, grouped toolbar, readable column (#227)
Full design polish pass (behavior unchanged; JSX structure + CSS only): - Chapters render as distinct section bars (heading title + grip + delete) with an accent left-border — no speaker/voice/tune/preview controls. Detection (isChapterText) is lenient so clearing the title doesn't flip the bar back to a voiced line mid-edit; unified with the chapter auto-numberer. - Toolbar split into three labelled clusters with thin dividers — Project (Projects · Cast) · Content (Import · Paste&Split · +Line · +Chapter) · Output (Stems · format · Generate) — and wraps instead of cramming one row. - Editor centered at a 1040px reading column so lines no longer stretch edge-to-edge on wide windows. - A line's secondary actions (inline-voice / tune / pause / preview / delete) are quieted to 0.5 opacity and revealed on row hover/active, cutting visual noise. Drag handlers factored into a shared dragProps (reused by both bar and line) so reordering still works across chapters + lines. Verified: tsc clean, build OK, vitest 167/167. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
94f2363fa2 |
feat(release): version preview builds (0.3.0-preview.N) + rollback spec (#226)
Phase A: stamp each preview build with a unique monotonic semver prerelease (<base>-preview.<run_number>) via an ephemeral tauri.conf.json rewrite on the preview path. Today every preview reported the static 0.3.0, so the updater never saw a newer version and never delivered preview updates. The prerelease ordering makes each new preview offer-able and converges to stable when <base> ships. (Windows MSI ProductVersion strips the prerelease — caveat noted to verify; mac/linux unaffected.) Phase B (rollback) is captured as a design spec for review, not implemented: per-version preview releases + retention, an in-app Preview-builds picker, an allow_downgrades install path, and the alembic-head data-safety boundary. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
675cc203ee |
fix(asr): cap setuptools <80 so pkg_resources stays present (#224) (#225)
Windows v0.3 users hit two transcription failures with models installed: - WhisperX: "Transcription produced no segments. No module named 'pkg_resources'" - Whisper PyTorch fallback: "No ASR backend is ready. … set OMNIVOICE_PRELOAD_TTS_ASR=1" Shared root cause: whisperx / faster-whisper import `pkg_resources` at runtime, and setuptools 80+ DROPPED the bundled pkg_resources. The existing pin `setuptools>=75` therefore resolved to 82.0.1 — which has no pkg_resources — so `import whisperx` fails. That both breaks WhisperX transcription and makes its is_available() return false, which is why every backend reports "not ready" and the engine asks for the PyTorch fallback (the user's PowerShell env var never reached the GUI-launched app, a separate red herring). Fix: pin `setuptools>=75,<80`. Verified: <80 resolves to 79.0.1 which ships pkg_resources; 82 does not. `uv lock` changed only setuptools (82.0.1→79.0.1). This fixes BOTH errors — WhisperX imports again, so it's available and the fallback is no longer needed. Adds tests/test_pkg_resources_available.py to guard the pin from regressing. Full suite 602 passed (incl. the new test), 0 failures. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
6a86d2151f |
refactor(settings): move Performance toggles to the General tab (#223)
The Performance panel (Disable torch.compile / Show live system metrics in header) was nested under the Credentials tab — an odd home. Render it in the General tab instead, where users look for app-level toggles. Pure relocation: PerformancePanel is unchanged; removed its render from CredentialsTab and added it after GeneralTab in the general view. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5320b57de5 | fix(release): macOS smoke mount path preserves space in volume name (#221) | ||
|
|
fa9cda3381 | fix(release): macOS signing env must be ABSENT not empty (unblocks mac build) (#220) | ||
|
|
3fd326b845 |
feat(update): dismiss button for the failed-update pill (#219)
#216 stopped the 6h periodic re-check from clobbering a failed-install error badge — correct, but it left no way to clear that badge except retrying, so a transient install failure pins a red "Update failed · Retry" pill until the user retries or restarts the app. Add a × to dismiss it (mirrors FloatingPill), returning the updater surface to idle. - updaterSlice: dismissUpdate() → idle + clears error/progress - UpdateBadge: × dismiss button on the error state (i18n: update.dismiss) - updaterSlice.test: dismiss returns to idle and clears the error - en.json: update.dismiss ("Dismiss"); other locales fall back to en Verified: vitest 10/10 (updaterSlice + updater guard), typecheck:ci clean, build OK. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
6aa581c5f1 | fix(release): resolve AppImage path before cd in Linux smoke (exit 127) (#218) | ||
|
|
1ece49a080 |
fix(release): make macOS signing opt-in so a bad cert can't break builds (#217)
The APPLE_CERTIFICATE secret is currently set-but-invalid, so tauri-action's 'security import' fails and kills the whole macOS build — on stable v* releases too, not just preview. Make Developer-ID signing OPT-IN: pass the Apple creds only on a v* tag push AND when the repo variable MACOS_SIGNING_ENABLED == 'true'. Otherwise pass empty -> the build stays unsigned and succeeds (users clear quarantine via xattr -cr, as documented). Preview is always unsigned. To re-enable signed stable releases: fix the signing secrets, then set MACOS_SIGNING_ENABLED=true (Settings -> Secrets and variables -> Actions -> Variables). No code change needed to flip it. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
679b2b5e4f |
fix(update): keep error/retry pill across periodic re-check (#214 follow-up) (#216)
* fix(update): keep error/retry pill across periodic re-check (#214 follow-up) PR #214's 6h periodic re-check guard skipped only downloading/ready, not error. setUpdateChecking() clears updateError and the badge renders null for 'checking', so a tick while 'Update failed · Retry' was showing silently erased the prompt the user still needed to act on — defeating the PR's own error-surfacing goal (greptile P1, unresolved). - updater.js: also short-circuit the re-check on 'error'. Retry is user-initiated (installUpdate → downloading), so auto re-check is unnecessary in that state. - updater.test.js: new regression test — guard no-ops on error/downloading/ready, proceeds from idle. - UpdateBadge.jsx: add aria-controls + panel id to the 'What's new' disclosure (greptile P2 a11y). - UpdateBadge.css: word-break:break-word → overflow-wrap:break-word (CodeRabbit; the deprecated value). Verified: vitest 166/166, typecheck:ci clean, bun run build OK, CJK guard pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(update): keep notes panel mounted so aria-controls always resolves greptile P2: aria-controls pointed at a conditionally-rendered panel, so the IDREF dangled while collapsed. Render the panel whenever notes exist and toggle with the hidden attribute (canonical disclosure pattern) — the reference now always resolves. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
891e819b4e |
fix(release): unblock all-platform preview/release builds + auto-generated notes (#215)
The first preview build surfaced four real release-pipeline issues (all of which also affect a stable v* release): - macOS: build died at codesign — `security import: failed to import keychain certificate` (the APPLE_CERTIFICATE secret is set but invalid). Preview now force-skips Apple signing (passes empty creds) so it can't fail on a bad/absent cert; stable v* tags still receive the secrets, so signing engages once the cert is fixed. - Linux: .deb bundling fails with "Failed to create control scripts: No such file or directory" (no custom deb config of ours). Drop .deb, ship AppImage only — the universal Linux format and the Linux auto-update target. - Installer smoke (all 3 OSes): the steps hunted for a frozen backend binary to boot with --health-check, but the thin uv-venv installer ships no such binary (the venv builds on first launch). Rewrite to structural verification — assert the bundle carries the shell binary + bundled uv sidecar + backend source resources (pyproject.toml + backend/main.py). Also: a new preview-notes job regenerates the rolling preview release body with GitHub's auto-generated notes (What's Changed by PR + New Contributors + Full Changelog) plus a Contributors avatar strip built from the PR authors — instead of the bare "Auto-generated release for main…" fallback. Runs once after the matrix, preview-only; stable keeps its CHANGELOG section + appended checksums. Stable v* tag-push behavior is otherwise unchanged. YAML validated. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
8cf3dad2f4 |
feat(gallery): multilingual designed-voice archetypes (#213)
* feat(gallery): multilingual designed-voice archetypes Ship curated designed voices in 9 more languages (Spanish, French, German, Italian, Portuguese, Russian, Hindi, Japanese, Korean) so the gallery offers more than English + Chinese out of the box -- 27 new featured archetypes across three reusable roles (Narrator / Explainer / Companion). Voice-design timbre (gender/age/pitch) is language-independent, and a designed voice's spoken language is driven by the preview text, not the instruct. So these reuse a neutral instruct + a localized sample script + a `language` value matching frontend/src/languages.json -- byte-for-byte the same model.generate(text, language, instruct) call the Generate tab already makes. They carry no accent/dialect token (accents are English-only, dialects Chinese-only; an invented "spanish accent" would crash synthesis, the issue-#89 mode), so every instruct stays inside the validator vocabulary. - backend/core/archetypes.py: _ML_SAMPLES + _ML_ROLES + _make_multilingual() - frontend VoiceGallery: extend the language facet filter - tests: assert the 9 languages are present, neutral-timbre, valid-token - test_no_hardcoded_cjk: note JA/KO sample text in the existing allowlist entry Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(gallery): surface featured-only languages through the filter Address review feedback (greptile/coderabbit) on #213: - VoiceGallery: the Browse query hard-coded `featured: false` while the Featured strip is hidden whenever a filter is active. The 9 new languages have *only* featured archetypes, so selecting Spanish/French/etc. produced an empty Browse AND a hidden Featured strip -> "No voices match these filters" despite 3 archetypes existing per language. Now Browse includes featured exactly when the Featured strip is hidden (i.e. when filtering), with no duplication when nothing is filtered. - archetypes.py: module docstring said the Featured tier was "~24"; it is now ~51 (24 English + 27 multilingual). Added a docstring to _make_multilingual(). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
edbf0af43f |
feat(update): release notes in badge + periodic re-check + error surfacing (#214)
Closes the three highest-value gaps from the auto-update audit vs best-in-class: 1. Release notes — the available pill gains a 'What's new' expander showing the release body (already captured as updateNotes) so users see what changed before installing. 2. Periodic re-check — App.jsx re-checks every 6h, not only on boot, so long-running sessions get notified. checkForUpdate now no-ops while a download/restart is in flight, so the interval can't interrupt an install. 3. Error surfacing — the badge no longer returns null on 'error'; it shows a 'Update failed - Retry' pill (with the error as tooltip) that re-attempts the install, instead of silently vanishing. i18n: update.whats_new/failed/retry added to en + zh-CN and backfilled across all 21 locales (placeholders intact). Verified: tsc clean, vitest 162/162, build OK, CJK guard passes, 21 locales valid + key-complete. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c4d66a8cf5 |
docs(readme): add ASR Engines table (surfaces SenseVoice/FunASR) (#212)
* docs(readme): add ASR Engines table (surfaces SenseVoice/FunASR) The README documents the multi-engine TTS backend but never listed the ASR backends, so users filed requests (#206, #208) for engines that already ship. Add an ASR Engines section mirroring the TTS table, grounded in backend/services/asr_backend.py engine ids + display names, plus a nav anchor. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(readme): correct Parakeet language scope + MLX framework name Address review feedback on PR #212: - Parakeet TDT: NeMoASRBackend docstring documents 25+ European languages w/ auto language detection (not English-only); note GPU req. - MLX Whisper: the engine uses Apple's MLX (Metal-backed) framework, not the CoreML inference stack. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
cf816b54f7 |
fix(ci): skip supertonic license/cpu tests when the optional dep is absent (#211)
release.yml installs deps with plain 'uv sync' (no optional engines), so test_cpu_only_honest and test_license_gate failed there — is_available() short-circuits with 'supertonic package not installed' before reaching the license check those tests assert on. This blocked EVERY release (preview and stable) at the test gate, not just the preview build that surfaced it. Skip the two when 'supertonic' isn't importable (optional opt-in engine). They still run fully under ci.yml's 'uv sync --all-extras'; the absent-package path is covered independently by test_optional_dep_missing. Also fixes the same two failing on a local '.venv' without the extra. Verified: tests/test_supertonic3.py now 8 passed, 5 skipped, 0 failed without supertonic installed. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
47729057bd |
chore(lint): remove unused imports + variables (ruff F401/F841) (#210)
Autofixes the genuine lint behind the CodeQL py/unused-import and py/unused-local-variable note-level alerts — actually removing the dead code rather than dismissing it. 68 safe fixes via 'ruff check --select F401,F841 --fix' across 29 backend files (dead stdlib/symbol imports like io/sys/json/torch/typing.Optional and unused locals). Only ruff's safe fixes applied — the 9 'unsafe' fixes and the audio_dsp numpy availability import were left untouched. Not touched: empty-except (needs per-site judgement, not autofixable); frontend js/unused-local-variable (eslint no-unused-vars has no autofix); the loopback-low-risk path/log/stack-trace alerts (real, left visible). Verified: full tests/ suite unchanged at 601 passed (the 2 test_supertonic3 failures are pre-existing on main, local .venv state, green in CI). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
253122325c |
feat(gallery): lucide/flag icon redesign + community marketplace (omnivoice-gallery) (#207)
* feat(gallery): lucide icons + country flags + card redesign (replace emoji) - backend archetypes emit lucide-react icon *names* (cross-platform; emoji render inconsistently across OSes) for use-cases and the 24 featured voices. - new frontend/src/utils/archetypeIcons.jsx: name→lucide map, accent→country flag (country-flag-icons, tree-shaken to ~11), per-category color scale, color-coded avatar tile, and a CSS-animated now-playing equalizer (prefers-reduced-motion aware). - card redesign: real elevated surfaces (cards were invisible on the dark bg), avatar + name + facet sub-line, accent/flag chips, and a footer with Preview / category-colored "Use voice" / Open-in-Designer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(marketplace): community voice gallery via omnivoice-gallery submodule Offloads curated + community gallery content to the standalone debpalash/omnivoice-gallery repo — added here as a submodule for authoring, loaded at runtime via the jsDelivr CDN so the binary stays small. Content repo (seeded + pushed separately): manifest.json (24-voice starter pack generated from the featured archetypes), a JSON schema, CONTRIBUTING, and GitHub submission templates carrying consent / no-impersonation guardrails. Backend (api/routers/community.py): configurable sources (env var > file > default), CDN fetch with offline disk cache, strict validation (invalid presets and non-allow-listed audio URLs are dropped, so a bad community entry can neither crash synthesis nor fetch from an arbitrary host), filtering, the prefilled submit URL, and "use" (preset → archetype render path; voice → sha256-verified download). 11 tests. Frontend: a third gallery zone, "Community", reusing the redesigned card, plus "Submit a preset / voice" buttons opening the prefilled GitHub forms. Local-first preserved: network only on open/refresh; everything cached; the built-in generated archetypes need no network, so the gallery is never empty. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(marketplace): address review feedback on the community gallery - community_use: run the blocking manifest read + voice download in a thread (asyncio.to_thread) so they don't stall the event loop (greptile P2). - community_submit_url: validate the `source` override against an owner/repo pattern, falling back to the configured default (greptile P1 hardening). - rename the `type` query param to `item_type` (alias="type") so it no longer shadows the Python builtin (coderabbit). - frontend submit buttons use the canonical openExternal() (Tauri-aware) instead of window.open, which doesn't open the system browser in the desktop app. - lowercase the `currentcolor` CSS keyword (stylelint). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(marketplace): rename useCommunityItem -> addCommunityItem (not a hook) Avoids the use-prefix on a plain API function (rules-of-hooks smell). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
9e04d5c683 |
i18n: backfill gallery + archetypes keys across 20 locales (#205)
The de-celebrified Voice Gallery (#203) added gallery.* (45) + archetypes.* (15) keys to en.json only, so all 20 other locales fell back to English for the new gallery UI. Backfill all 60 keys into every locale via the project's own scripts/translate_all.py (Google Translate, placeholder-masked, incremental) — the same tool/path fixed in the earlier update-channel backfill. Also removes 8 stale gallery.cat_* keys per locale (cat_celebs, cat_marvel, cat_disney, cat_politicians, cat_anime, cat_books, cat_gaming, cat_news) — the dead celebrity categories #203 removed from en.json but left behind in the other locales. Finishes the de-celebrification across the whole i18n layer. en.json / source untouched. Verified: all 21 locales valid JSON and key-aligned to en for gallery + archetypes (0 missing); {{version}}/{{pct}}/{{channel}}-style placeholders intact (0 losses); tsc clean; build OK; CJK guard passes (locales are the allowlisted translation layer). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
fd6f213517 |
fix(audio): stop near-silent renders becoming blank noise + guard archetype renders (#204)
* fix(audio): stop near-silent renders becoming "blank noise" + guard archetypes Root cause of the blank/hiss voices: normalize_audio peak-normalized to -2 dBFS whenever max(|audio|) > 0. When the model emits a near-silent clip (peak at the noise floor, e.g. 1e-4), that applies thousands of × of gain and lifts the noise floor to full scale — silence turned into loud hiss. This affected every generation path (clone/dub/design/archetypes), which is why "some voices" came out as blank noise. - services/audio_dsp.py: normalize_audio gains a -50 dBFS silence floor. At or below it the audio is left untouched (stays inaudible) instead of being amplified. Real speech — even a whisper — peaks well above the floor, so normal output is unchanged. - api/routers/archetypes.py: after rendering, _is_blank_audio() detects a dead clip (empty / non-finite / peak < 0.02 — a real normalized clip peaks ~0.79). The render retries once with a different seed, then fails loudly (503 via the existing handlers) so a blank preview or voice profile is never cached/saved. Also extracts the script with a non-empty fallback. - core/archetypes.py: _build never falls back to an empty script (empty text synthesizes to silence). Tests (tests/, runs in CI): normalize_audio doesn't amplify silence but still normalizes real audio to target; _is_blank_audio flags dead renders and passes real audio; every archetype carries a non-empty sample script. Verified: full tests/ suite 601 passed incl. 8 new (the 2 test_supertonic3 failures are pre-existing on main — local .venv engine/license state, green in CI — and unrelated to this diff). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(gallery): static log message in blank-render retry (clears py/clear-text-logging) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
e1850b4bd9 |
feat(gallery): designed-voice archetype gallery + neutral importer (#203)
* feat(gallery): designed-voice archetype gallery + neutral importer Adds a browsable library of ~1,100 designed voice archetypes (no real people), generated from OmniVoice's own voice-design taxonomy and organized ElevenLabs-style: 24 curated Featured voices plus a facet-filtered "Browse all" explorer (595 English + 504 Chinese-dialect). Every generated instruct is built from the validator's own vocabulary, so none can trigger the issue-#89 synthesis crash. Backend: - core/archetypes.py: catalog engine (featured + generated, implausible combos pruned, stable hashed ids); loads the taxonomy by file path to stay torch-free in tests. - api/routers/archetypes.py: categories / list+filter+paginate / get / preview (render-on-demand + disk cache) / use (materialize a voice profile). Preview/use reuse generation.py's proven inference path. - gallery.py: drop the celebrity/character catalog; the importer is now a neutral, user-driven "My Imports" (paste a URL you have the rights to). No project-shipped directory of named real people. Frontend: - Gallery UI rewrite: Archetypes zone (featured grid + facet filters + favorites) and My Imports zone; per-card Use voice / Open in Designer. - api/archetypes.ts, useArchetypes/useArchetypeCategories hooks (v5 placeholderData:keepPreviousData), gallerySlice, en.json keys. Tests: 27 new (engine contract + API), full backend suite green (72); CJK guard allowlists the one functional Chinese preview sample. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(gallery): clear new bandit alerts (sha1 + SQL false-positive) The PR's code-scanning "Bandit" check fires on NEW alerts vs main's baseline. The archetype work introduced three: - archetypes.py / core/archetypes.py: hashlib.sha1 used to derive a deterministic preview-cache key and archetype id (not a security digest) — flagged B324 (HIGH). Add usedforsecurity=False; the digest is unchanged. - gallery.py: the UPDATE query interpolates only static, code-controlled column fragments ("is_favorite = ?", "description = ?"); every user value is bound via a ? placeholder — flagged B608 (false positive). Annotate `# nosec B608` with the justification. Behavior-preserving. Net new bandit alerts after this: zero (verified with bandit -ll -ii; only main's pre-existing baseline remains). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(gallery): resolve PR #203 CI (SHA-256 ids, log sanitization, CJK allowlist) All failures stemmed from the initial commit: - Bandit + CodeQL (2 high): SHA-1 weak-hash on the archetype id and the preview cache key. These are deterministic identifiers, never security digests — switched to SHA-256, which the SAST scanners accept. - CodeQL (log injection): the render-failure logs echoed the raw user-supplied archetype_id; log the catalog's canonical a["id"] instead (untainted — it comes from the trusted in-memory catalog, not the request). - CodeQL (superfluous argument): declare createGallerySlice's StateCreator store param so its arity matches the 3-arg call site. - Tests (test_no_hardcoded_cjk): the committed design spec's Chinese-dialect reference table tripped the guard; allowlist it under documentation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(gallery): clear CodeQL clear-text-logging on archetype render errors CodeQL's sensitive-data heuristic flags any request-derived value interpolated into a log call (it persisted even after switching the raw id to the catalog's canonical a["id"]). Log a static message with exc_info=True instead: the full traceback still reaches the backend log for debugging, but no data expression remains for the query to flag. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |