454affb6e9efe05e8379e5d436392b497a3f6708
344
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> |
||
|
|
5d602c8871 |
fix(tts): let studio engines skip the broadcast mastering chain (#311)
* fix(tts): let studio engines skip the broadcast mastering chain apply_mastering() (HighpassFilter + Compressor + 8% Reverb) is tuned for OmniVoice's 24 kHz clone output. The OpenAI-compatible /v1/audio/speech route (_run_tts) runs it on every engine, including VoxCPM2 — whose native 48 kHz output is already studio-grade. There the compressor pump and the reverb tail are audible degradation rather than polish. Add an opt-out class flag TTSBackend.applies_own_mastering (default False, so all existing engines are unchanged) and set it True on VoxCPM2Backend. _run_tts() skips apply_mastering() when the active backend declares it. Loudness normalisation still runs for every engine (benign peak scale). * fix(tts): also skip mastering on the streaming route for studio engines tts_stream.py is the other route that runs the *active* TTS backend (get_active_tts_backend), so it needs the same applies_own_mastering guard as openai_compat._run_tts — otherwise VoxCPM2 output is still pumped/reverbed when streamed. The remaining apply_mastering() call sites (generation.py, batch.py, batched_tts.py, dub_generate.py) run the OmniVoice model directly via get_model(), never the active backend, so VoxCPM2 cannot reach them. * docs(tts): mark OmniVoice-only mastering sites with TODO(#312) Per review: instead of always-False guards on routes that never run the active backend, leave a pointer so the applies_own_mastering guard is added exactly when those routes become engine-aware (issue #312). |
||
|
|
5ba8a5a8a0 |
fix(gguf): forward speech generation controls (#306)
Co-authored-by: openclawer <bdfzer8@gmail.com> |
||
|
|
e7f78bffef |
fix: disable tqdm on non-TTY to prevent OSError on Windows (#305)
* fix: disable tqdm on non-TTY to prevent OSError on Windows (#283) When running as a Tauri backend (non-TTY stdout), tqdm tries to write terminal control characters which fails with Errno 22 on Windows. Set TQDM_DISABLE=1 when stdout is not a TTY during model loading. * fix: guard sys.stdout against None and fix import ordering (#283) - Add None check before calling isatty() to prevent AttributeError - Fix import ordering (sys after re alphabetically) |
||
|
|
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> |