d0517fdb8783fd975121eadc0f137168feb96217
46
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
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> |
||
|
|
35b62ad0c0 |
fix(ci): make SHA-256 checksum step bash-3.2 safe (macOS runner) (#265)
The "Compute SHA-256 checksums" step used `mapfile -t` (a bash 4+ builtin) but
macOS GitHub runners execute `shell: bash` as /bin/bash 3.2, which has no
`mapfile`. The step exited 127 ("mapfile: command not found") on the macOS leg,
so `SHA256SUMS-macOS Apple Silicon.txt` was never produced/uploaded for v0.3.1
and v0.3.2 (the binaries themselves shipped fine; only the macOS checksum file
was missing and had to be regenerated by hand each time).
Replace `mapfile` with a portable `while IFS= read -r … done < <(find … | sort)`
loop (works on bash 3.2). Verified on bash 3.2.57: builds the array correctly,
handles spaces in bundle filenames. Linux/Windows legs are unaffected.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
ab492c0e8e |
fix(docker): fix stale :latest tag + add push-to-main trigger (#252)
* fix(docker): fix stale :latest tag and add push-to-main trigger (closes #251, addresses #249) Two bugs caused the Docker image to be stale (showing v0.2.7 inside a :latest/:0.3.x-tagged image): 1. **`:latest` was never set on tag pushes.** The metadata-action rule `type=raw,value=latest,enable={{is_default_branch}}` evaluates `is_default_branch` as false on tag-triggered runs (which run in a detached-HEAD context, not on the default branch). The tag rule was replaced with `enable=${{ github.ref_type == 'tag' }}` so `:latest` is updated on every `v*` tag push. 2. **No trigger for main-branch pushes.** There was no way to keep an up-to-date `:main` edge image between releases. Added `push: branches: [main]` which produces a `:main` rolling tag. Also added a note in the workflow and docs clarifying that the update-channel toggle (Settings → About) is a Tauri desktop feature and does not apply to the Docker image (headless web-server build). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(docker): gate mutable tags to push events + guard :latest against prereleases (PR #252 review) - semver / :latest / :main now require github.event_name == 'push' so a manual workflow_dispatch only ever emits a throwaway :sha- tag (no mutable-tag rollback) - :latest excludes prerelease tags (ref contains '-') so an rc/beta can't clobber it - header + tag-strategy comments corrected (:sha- emits on every trigger) Addresses greptile + coderabbit review on #252. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
94f2363fa2 |
feat(release): version preview builds (0.3.0-preview.N) + rollback spec (#226)
Phase A: stamp each preview build with a unique monotonic semver prerelease (<base>-preview.<run_number>) via an ephemeral tauri.conf.json rewrite on the preview path. Today every preview reported the static 0.3.0, so the updater never saw a newer version and never delivered preview updates. The prerelease ordering makes each new preview offer-able and converges to stable when <base> ships. (Windows MSI ProductVersion strips the prerelease — caveat noted to verify; mac/linux unaffected.) Phase B (rollback) is captured as a design spec for review, not implemented: per-version preview releases + retention, an in-app Preview-builds picker, an allow_downgrades install path, and the alembic-head data-safety boundary. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5320b57de5 | fix(release): macOS smoke mount path preserves space in volume name (#221) | ||
|
|
fa9cda3381 | fix(release): macOS signing env must be ABSENT not empty (unblocks mac build) (#220) | ||
|
|
6aa581c5f1 | fix(release): resolve AppImage path before cd in Linux smoke (exit 127) (#218) | ||
|
|
1ece49a080 |
fix(release): make macOS signing opt-in so a bad cert can't break builds (#217)
The APPLE_CERTIFICATE secret is currently set-but-invalid, so tauri-action's 'security import' fails and kills the whole macOS build — on stable v* releases too, not just preview. Make Developer-ID signing OPT-IN: pass the Apple creds only on a v* tag push AND when the repo variable MACOS_SIGNING_ENABLED == 'true'. Otherwise pass empty -> the build stays unsigned and succeeds (users clear quarantine via xattr -cr, as documented). Preview is always unsigned. To re-enable signed stable releases: fix the signing secrets, then set MACOS_SIGNING_ENABLED=true (Settings -> Secrets and variables -> Actions -> Variables). No code change needed to flip it. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
891e819b4e |
fix(release): unblock all-platform preview/release builds + auto-generated notes (#215)
The first preview build surfaced four real release-pipeline issues (all of which also affect a stable v* release): - macOS: build died at codesign — `security import: failed to import keychain certificate` (the APPLE_CERTIFICATE secret is set but invalid). Preview now force-skips Apple signing (passes empty creds) so it can't fail on a bad/absent cert; stable v* tags still receive the secrets, so signing engages once the cert is fixed. - Linux: .deb bundling fails with "Failed to create control scripts: No such file or directory" (no custom deb config of ours). Drop .deb, ship AppImage only — the universal Linux format and the Linux auto-update target. - Installer smoke (all 3 OSes): the steps hunted for a frozen backend binary to boot with --health-check, but the thin uv-venv installer ships no such binary (the venv builds on first launch). Rewrite to structural verification — assert the bundle carries the shell binary + bundled uv sidecar + backend source resources (pyproject.toml + backend/main.py). Also: a new preview-notes job regenerates the rolling preview release body with GitHub's auto-generated notes (What's Changed by PR + New Contributors + Full Changelog) plus a Contributors avatar strip built from the PR authors — instead of the bare "Auto-generated release for main…" fallback. Runs once after the matrix, preview-only; stable keeps its CHANGELOG section + appended checksums. Stable v* tag-push behavior is otherwise unchanged. YAML validated. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
4ec5b4cb7d |
chore(security): scope CodeQL to shipped product code (#201)
CodeQL flagged 459 alerts on main, but triage showed the bulk are in code
that never ships in the installer's runtime path: file-not-closed in the
omnivoice/eval harnesses, unused-global "FPs" in alembic migration boilerplate
(revision/down_revision), bind-all in tests, and path sinks in the legacy
Gradio research UI. They drowned out the handful of real findings.
Add a CodeQL config (inline, supported because build-mode is `none`/interpreted)
that scopes analysis to product code via paths-ignore: omnivoice/eval, research,
tests, backend/migrations, and *.test.* files. Queries move into the inline
config so security-and-quality stays the single source of truth alongside
paths-ignore.
Net effect on the next scan: the non-shipped-code alerts auto-resolve, leaving
the security tab focused on shipped backend + frontend. No product code changes.
Deliberately NOT touched (assessed, left as-is):
- Stack-trace-exposure (detail=str(e) in routers) — these are intentional,
helpful one-line diagnostics (the error-transparency work in
|
||
|
|
672f106f05 |
feat(update): Stable/Preview update channels with opt-in toggle (#199)
Adds a user-selectable updater release channel (Settings -> About -> Update channel). Stable (default, every install + launch) tracks tagged vX.Y.Z releases; Preview tracks the latest main build via a rolling "preview" prerelease, falling back to stable if a stable release is ahead. Why Rust: tauri-plugin-updater reads its endpoints from tauri.conf.json and neither the JS check() nor the plugin's registration Builder can change them at runtime (verified against the 2.10.1 source). The only runtime-endpoint API is UpdaterExt::endpoints, so check+install move into two Rust commands that mirror the plugin's own check/download_and_install -- the Stable path behaves identically to the JS flow it replaces; only which manifest is consulted changes. Switching is instant (channel is read per check), no restart. backend (Rust): - config.rs: update_channel field (default "stable", VALID_CHANNELS) + get/set_update_channel commands. - updater_channel.rs: channel_endpoints() (preview -> [preview, stable]) + check_update / install_update commands; install emits update://progress. frontend: - utils/updateChannel.js (+test): single source of truth, normalizeChannel. - utils/updater.js: routes the badge flow (#198) through the Rust commands via the same store contract -- UpdateBadge/App.jsx unchanged. - Settings About: Stable/Preview segmented toggle, channel-aware endpoint row + diagnostics; Check-for-updates honors the live channel. - i18n en + zh-CN. release.yml: additive, workflow_dispatch-guarded preview publish to a rolling "preview" prerelease. The v* tag-push stable path evaluates to its exact prior values (verified) and is never affected. Preview builds are manual -- no scheduled CI spend, nothing auto-published. docs/update-channels.md. Verified: cargo check (compiles clean), tsc, vitest 162/162, build, CJK guard, release.yml YAML parses. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ee671300be |
ci: gate omnivoice-tts build to pin changes; drop hanging Intel-Mac leg (#147)
* ci: gate omnivoice-tts build to pin changes; drop hanging Intel-Mac leg The omnivoice-tts C++ runtime is pinned to a commit SHA in quant_map.json, so it only needs rebuilding when that pin (or the build script) changes — not on every PR/push. Running it per-push left the heavily-contended hosted macOS runners (esp. Intel macos-13) sitting in "Waiting for a runner…" for hours as a perpetual queued check (the UNSTABLE state on every PR). - Moved the build out of ci.yml into its own workflow, .github/workflows/build-omnivoice-tts.yml, gated to: paths [quant_map.json, scripts/build-omnivoice-tts.sh, the workflow] + workflow_dispatch. Normal PRs no longer trigger (or hang on) it. - Dropped the Intel darwin-x86_64 (macos-13) matrix leg: that hosted pool is unusably contended and Apple's momentum is on arm64; Intel-Mac users get the in-process OmniVoiceBackend fallback (already the documented behavior). Kept linux-x86_64, windows-x86_64, darwin-arm64. Re-add macos-13 here if first-class Intel binaries are ever needed. Both workflows YAML-validated. Matches ci.yml's stated philosophy of keeping heavy platform builds off the per-PR path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci: timeout-minutes + injection-harden the omnivoice-tts build (bot review) - Greptile: add `timeout-minutes: 45` so a hung leg (esp. experimental darwin-arm64 Metal) can't run to GitHub's 6h ceiling — same resource-drain class this PR addresses. - CodeRabbit: stop interpolating the pinned SHA / platform directly into the run block. Validate the SHA is a git hash in the pin step, then pass it + platform via quoted env vars (no shell-injection surface from quant_map.json). Declined: SHA-pinning actions@v4 — matches the repo's floating-tag convention (ci.yml/release.yml); belongs in a repo-wide hardening pass + Dependabot. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
50954f7f43 |
feat(macos): wire Developer-ID signing + notarization; fix "app is damaged" docs (#134, #72) (#143)
The unsigned DMG triggers macOS Gatekeeper's misleading "app is damaged" block (#134, #72). Two parts: - release.yml: pass APPLE_CERTIFICATE / _PASSWORD / APPLE_SIGNING_IDENTITY / APPLE_ID / APPLE_PASSWORD / APPLE_TEAM_ID to tauri-action. It signs + notarizes the macOS bundle when these repo secrets are set, and is a no-op (today's unsigned build) when they're absent — so this is safe to merge now and "activates" the moment the maintainer adds an Apple Developer cert. - docs/install/macos.md: explain the "damaged" message is Gatekeeper (not corruption), give the `xattr -cr` + right-click→Open workarounds, and add a "For maintainers" table of the required secrets. Removed the stale "tracked for v0.4" line (versioning rule: everything's on v0.3.0). The in-app error→docs deeplink (GATEKEEPER_QUARANTINE) already targets the #gatekeeper-quarantine anchor. Refs #134, #72. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
8162f52c08 |
ci(security): scanning workflow + CodeRabbit config + sweep design (PR 0) (#135)
* ci(security): add scanning workflow + CodeRabbit config + sweep design PR 0 of the v0.3.0 stabilization sweep — establishes the automated review + security gate every subsequent plan PR flows through. - .github/workflows/security.yml: gitleaks (gating secret scan), CodeQL (Python + JS/TS), bandit (SARIF), pip-audit + bun audit. Only the secret scan gates; dep/SAST findings are reporting-only to stay consistent with the no-ceremony, continuous-to-main cadence. - .coderabbit.yaml: path filters + constitution constraints encoded as review instructions (local-first, cross-platform parity, alembic, no secret/home-path leakage). Drafts excluded from auto-review. - SECURITY.md: document the automated scanning + bot review. - docs/specs: program design for the full sweep (plan-01..05 + PR triage). CodeRabbit and Greptile apps are already installed and will review on PR open. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci(security): install bandit[sarif] extra; pin JS actions to Node 24 The bandit SARIF formatter ships in the `bandit[sarif]` extra; plain `bandit` rejects `-f sarif` (exit 2), so no SARIF was written and the upload step failed. Install via `pipx run --spec 'bandit[sarif]'`. Also add FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 (mirrors ci.yml) to silence the Node 20 deprecation warning on checkout/setup-python/upload-sarif. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci(security): harden per bot review — persist-credentials, upload guard, bun pin Addresses CodeRabbit + Greptile findings on #135: - persist-credentials: false on all checkout steps (don't leave GITHUB_TOKEN in git config; none of these jobs need authed git after clone). [CodeRabbit] - continue-on-error on the bandit SARIF upload so a missing SARIF can't fail this reporting-only job. [Greptile P1] - pin bun-version "1.2" — `bun audit` only exists in bun >=1.2.x. [Greptile P2] Declined: full-SHA action pinning. Meets the major-tag bar set in .coderabbit.yaml and matches ci.yml/release.yml convention; SHA pinning belongs in a repo-wide hardening pass with Dependabot, not one file. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b34dcd9e11 |
Phase 4 Plan 04-01: SPIKE-01 GGUF — GO + integration (#100)
* Phase 4 Plan 04-01: SPIKE-01 GGUF — GO + Wave 1 integration Integrates Serveurperso/OmniVoice-GGUF as a hardware-adaptive default voice-cloning engine, with overridable fallback to the in-process OmniVoiceBackend. Spike confirmed GO: the model is a clean quantization of k2-fsa/OmniVoice (Apache-2.0 + MIT runtime, `omnivoice-lm` custom architecture so it does NOT load in vanilla llama.cpp). Pinned SHAs: * Serveurperso/OmniVoice-GGUF revision: 361609388ae572a820d085185bbbe2a2aac4b30e * ServeurpersoCom/omnivoice.cpp master: 886fc079838ca7400cb2b42b36e2a65aa1daabe8 Implements GGUF-01 (hardware probe) through GGUF-05 (default-engine resolver with graceful fallback). The four `bin/omnivoice-tts-*` artifacts are committed as zero-byte placeholders; the new CI matrix job builds the real binaries per platform from the pinned commit SHA and appends a SHA-256 manifest used by `is_available()` for tampering detection (T-04-01). The macos-14 (Apple Silicon) slot is marked `continue-on-error: true` because omnivoice.cpp publishes no `buildmetal.sh` (Pitfall 1 / Assumption A1) — failure feeds into Task 3's GO/NO-GO call. Quant override is allow-listed against quant_map.json entries only (T-04-05). Argv is composed from typed Path objects rooted in HF_HUB_CACHE; never uses `shell=True`. HF token redaction applies to captured stderr before logging (AUTH-05 / T-04-04). Tests: 36 new (8 hardware-probe + 13 GGUF engine + 6 settings_store quant override + grep gate); 428 passed in full suite vs 402+ baseline. ADR Status stays "Proposed (research-supported)" — Task 3 (human checkpoint) flips to Accepted after CI produces real binaries and a reviewer signs off on the GGUF-06 cross-hardware smoke. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: install libopenblas-dev on linux-x86_64 omnivoice-tts build The pinned omnivoice.cpp commit (886fc079...) ships a `buildcpu.sh` that passes `-DGGML_BLAS=ON`. ubuntu-latest has no BLAS implementation preinstalled, so the cmake configure step fails with `Could NOT find BLAS (missing: BLAS_LIBRARIES)` and the job exits in 13 s before producing the linux-x86_64 binary. macOS (Accelerate, built in) and Windows (BLAS off by default in the ggml CMakeLists for non-APPLE platforms — the build script doesn't invoke buildcpu.sh on those slots) are unaffected and stay green. Adds a Linux-gated apt step to install libopenblas-dev + pkg-config before the build, restoring cross-platform parity per the CLAUDE.md "default features must work on every platform" rule. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(gguf): constrain ref_audio to project roots — block /etc/shadow on Linux The GGUF engine's `_build_argv` previously validated ref_audio only via `ref_path.is_file()` — i.e. "does this path exist?" That check is platform-dependent: `/etc/shadow` doesn't exist on macOS (rejected naturally), but it IS a real system file on Linux, so the validation silently accepted it. CI's ubuntu-22.04 runner exposed the gap via `test_generate_blocks_freeform_ref_audio`, which exists precisely to guard the "freeform ref_audio path" attack surface. Fix: confine ref_audio to one of three allowed roots before existence checks: - VOICES_DIR (user-saved voice profiles) - DUB_DIR (per-job auto-clones extracted from source video) - tempfile.gettempdir() (browser-upload temp files; existing `cleanup_ref` flow in generation.py) Anything outside those roots → FileNotFoundError, matching the existing failure-mode contract callers handle. Existence check still runs after, so the test's mocked subprocess.run is never reached and the test passes deterministically on all three platforms. Cross-platform parity (per CLAUDE.md 2026-05-20 rule): identical behaviour on macOS / Windows / Linux — the allow-list is computed from core.config which uses platform-specific path resolution but yields the same logical "project tree" on every OS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(gguf): mark darwin-x86_64 binary build as experimental GitHub's macos-13 (Intel) runner pool is heavily contended — PR #100 queued for 30+ minutes waiting on darwin-x86_64 while every other platform finished in ~1m. Intel Macs are also fading hardware (Apple's platform momentum is entirely on Apple Silicon), and the GGUF engine's runtime already handles a missing binary gracefully (`is_available()` returns False on Intel Mac with a "binary not bundled for this platform" message, same path used for first-launch before any binaries build). `experimental: true` mirrors what darwin-arm64 (Metal) already has — slot still runs and uploads its binary when successful, but a failure or runner backlog no longer blocks merges. Keeps the GGUF engine shippable across the dominant arm64 / Linux / Windows surface without holding the inbox on a slow-runner queue. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
93aa66ab0a |
Phase 3 Plan 03-01: Supertonic-3 engine on SubprocessBackend (#101)
* Phase 3 Plan 03-01: Supertonic-3 engine on SubprocessBackend
Adds Supertonic-3 as a 7th opt-in TTS engine on the Phase 2
SubprocessBackend primitive. Closes TTS-01..06 (REQUIREMENTS.md):
* TTS-01 — _REGISTRY["supertonic3"] resolves to Supertonic3Backend,
a SubprocessBackend subclass.
* TTS-02 — `supertonic==1.3.1` lives under [project.optional-dependencies];
default `uv sync --no-dev` does NOT install it. Exactly one
`onnxruntime` row in `uv pip list` after `--extra supertonic`.
* TTS-03 — Model revision pinned by 40-char commit SHA
(724fb5abbf5502583fb520898d45929e62f02c0b — the "Initial
Supertonic 3 release" SHA, same as the SDK's own pin).
Resolver script for intentional bumps:
scripts/resolve_supertonic3_sha.py.
* TTS-04 — Honest CPU-only reporting. `is_available()` message says
"ready (CPU-only via onnxruntime)" and never mentions
"cuda" or "mps". `gpu_compat = ("cpu",)`.
* TTS-05 — License gate via settings_store helpers
(get/set_license_accepted) + Loopback-only
/api/settings/license endpoint + SupertonicLicenseDialog
frontend modal showing MIT (code) and OpenRAIL-M (model).
Wired into EngineCompatibilityMatrix as an "Accept license"
button on rows whose `reason` mentions "license not
accepted".
* TTS-06 — 3 langs (en/ja/ru) × 3 sec smoke test in
tests/test_supertonic3.py::test_smoke_3langs_3sec
(OMNIVOICE_SMOKE-gated; asserts no onnxruntime-gpu row
post-synthesize).
Package legitimacy gate (Task 1 in plan): supertonic on PyPI verified
to be published by Supertone Inc. (ato@supertone.ai), repo
github.com/supertone-inc/supertonic, wheel is pure-Python with no
postinstall scripts. Same publisher ships supertonic-js on npm under
the same maintainer email.
Test results:
* tests/test_supertonic3.py — 10 passed, 3 skipped (network-gated).
* tests/smoke/ — 4 passed.
* tests/ (full, --ignore=tests/manual) — 412 passed, 0 failed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ci(tests): uv sync --all-extras so optional-engine tests can import their package
Phase 3 added `supertonic` as an optional dependency. The CI Tests job
runs `uv sync` (no extras), so `test_cpu_only_honest` and `test_license_gate`
in tests/test_supertonic3.py hit the "supertonic package not installed"
fallback instead of the real import path, and fail.
Bare `uv sync` is the right default for users (engines are opt-in), but
the test environment should exercise the full surface. `--all-extras`
keeps the smoke job lean (still bare `uv sync`) while letting Tests
verify the integrated behavior of every optional engine.
Future-proofs against the same failure mode in Phase 4 (GGUF) and any
later optional engines.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
715766cb04 |
Phase 1 Wave 2: per-OS install docs + Settings UI + error→docs deeplinks (#94)
* docs(install): per-OS install pages + drift validator + CI gate
Splits the 600-line README install section into self-contained per-OS docs
under docs/install/{macos,windows,linux,docker}.md plus a Top-10
troubleshooting index. Each OS doc is end-to-end: a user opens it and
reaches a working app following only commands inside that file.
Adds:
- docs/install/{macos,windows,linux,docker}.md (OS-specific install paths)
- docs/install/troubleshooting.md (top 10 install errors)
- docs/engines/cosyvoice.md (closes #55 docs half)
- docs/features/diarization.md (pyannote license flow)
- docs/setup/huggingface-token.md (3-source cascade guide)
- scripts/validate-install-docs.py (INST-06 docs-drift gate)
- tests/scripts/test_validate_install_docs.py (B-5: validator self-tests)
- .github/workflows/ci.yml step running the validator on every PR
Implements INST-02 (README routing), INST-03 (macOS Gatekeeper anchor),
INST-12 docs half (Windows torch-compile-oom anchor), DOCS-01..05.
The validator is a one-way diff: every `<!-- validate -->`-tagged line
in docs must appear in scripts/desktop-prod.sh after normalisation
(prompt-prefix strip, CRLF, trailing whitespace, blank-and-comment skip).
A `<!-- validate: skip -->` marker opts out for human-readability blocks.
Its own 10 unit tests catch regressions in the gate itself.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(deeplinks): links.py + error_docs_map (Python + TS mirror)
Adds the single source of truth for the project repo URL and the 4-class
error → docs taxonomy that both the in-app ErrorBoundary deeplink button
(Wave 2 Task 3) and the Phase 5 bug reporter will consume.
New:
- backend/core/links.py — PROJECT_REPO_URL + BLOB_MAIN resolver
(Tauri config first, pyproject fallback)
- backend/core/error_docs_map.py — lookup(error_class) → docs URL
- frontend/src/utils/errorDocsMap.ts (TS mirror with classifyError helper)
- tests/backend/core/test_links.py + test_error_docs_map.py
- frontend/src/utils/errorDocsMap.test.ts
Resolves checker B-6 (links.py ownership) and Open Question #3 (which fork
the deeplinks resolve to — the Tauri updater endpoint wins, which points
at the desktop app fork debpalash/OmniVoice-Studio).
The TS BASE constant is documented as the second hardcoded URL drift site;
the keys-sync test (`test_keys_match_python_map` equivalent) guards the
4-class taxonomy contract between Python + TS halves.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ui): Settings → API Keys panel + ErrorBoundary docs deeplink
Wave 2 AUTH-03 UI half + ErrorBoundary deeplink wiring.
ErrorBoundary fallback now renders an "Open docs for this error" button
that classifies the thrown Error message (heuristic: pkg_resources → 401 /
HfHubHTTP → WebKit / white screen → quarantine / Gatekeeper) and opens the
matching docs anchor via Tauri shell.open (with a window.open fallback
in browser dev mode).
ApiKeysPanel consumes the Wave 1 resolver state endpoint:
- 3 source rows (App / Env var / HF CLI) with set/unset indicator,
masked token preview, whoami username + green check
- "Active" badge on whichever source is currently serving the cascade
- App-row only: Save (POST /api/settings/hf-token) +
Clear (DELETE with optional "also clear HF CLI" confirm dialog)
- "Test now" button refetches state (invalidates the resolver's
validation cache via the same endpoint hit)
Panel mounted in the existing Settings → Credentials tab; the legacy
HF_TOKEN row from CREDENTIAL_FIELDS is filtered out so the two paths
don't fight over the same key.
Threat T-02-02: the panel never displays the full token. The masked
value comes from the resolver state endpoint; the full token only
crosses the IPC boundary on Save (POST) and is cleared from local
state on success.
Closes AUTH-03 fully (Wave 1 backend + this Wave 2 UI).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(perf): INST-12 Disable torch.compile (Windows) toggle (backend + UI)
Wave 2 Task 4 — full INST-12 delivery per checker B-2/B-7 v0.3.0 fat-release
decision. Both the docs half (windows.md anchor, shipped in earlier commit)
and the runtime toggle are now in Phase 1.
Backend:
- backend/services/settings_store.py: adds get_text/set_text helpers for
non-secret config (refuses to write to the encrypted hf_token key).
- backend/api/routers/settings.py: GET + PUT
/api/settings/perf/torch-compile-disabled, both under the existing
loopback guard (threat T-02-04).
- backend/services/engine_env.py: new `build_engine_env()` helper that
centralises HF_TOKEN/YOUR_HF_TOKEN injection from the 3-source resolver
AND injects TORCH_COMPILE_DISABLE=1 when the flag is set on win32.
Phase 2 SubprocessBackend launchers should adopt the same helper.
- backend/services/sonitranslate.py: migrated to engine_env.build_engine_env()
while preserving the source-level `env["HF_TOKEN"]` sentinel that
test_sonitranslate_module_uses_resolver checks.
Frontend:
- frontend/src/components/settings/PerformancePanel.{jsx,css,test.jsx}:
toggle UI with the explainer for #65; renders disabled with a "not
applicable" badge on macOS/Linux.
- frontend/src/pages/Settings.jsx: mounts the panel into the Credentials
tab alongside the API Keys panel.
Tests:
- tests/backend/test_perf_settings.py: 7 backend tests (default state,
PUT persistence, T-02-04 non-loopback rejection, settings_store round-
trip, env injection on win32, NO injection on macOS/Linux, NO injection
when disabled).
- frontend PerformancePanel.test.jsx: 5 tests (renders from GET state,
PUT on toggle, disabled on non-Windows platforms, pre-enabled state).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(planning): Wave 2 SUMMARY + REQUIREMENTS status updates
- .planning/phases/01.../01-02-SUMMARY.md: full implementation report
per template (truths, commits, tests, deviations, drift-site
acknowledgments per W-3, launcher seam name for Phase 2,
taxonomy keys for Phase 5).
- .planning/REQUIREMENTS.md: flips Wave 2 closures to Done:
AUTH-03, INST-02, INST-03 (docs half), INST-06, INST-12,
DOCS-01..05.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
651e63b7e9 |
P0 wave-1: security + correctness + Phase 2 foundation (#88)
P0 security + correctness fixes plus Phase 2 foundation work. 7 atomic commits, all CI green (Smoke + Tauri shell on macOS/Win/Linux + Tests). Code commits: - |
||
|
|
e4dbf4c8c0 |
P0: release.yml typecheck + bind audit + loopback middleware (#84)
Three P0 fixes bundled — foundation cleanup before v0.3.0 phase work. Closes release.yml drift (PR #51's tabs broke v0.3.0 tag releases), production bind exposure (Critic F1), and 9-endpoint LAN gap on /system/* (Critic F2+F3). 5 new tests; 243 full pass. |
||
|
|
766e2f7284 |
Phase 0 — Gates: cross-platform CI matrix + regression fixture + release smoke (#71)
* docs: initialize OmniVoice stabilization milestone project * chore: add project config (yolo + balanced) * docs: domain research for stabilization milestone * docs: define v1 requirements for stabilization milestone * docs: add GGUF + singing engine spike requirements (Phase 4 new) * docs: roadmap revision + CLAUDE.md (7 phases, 62 reqs, +GGUF/SING spikes) * docs(phase-0): add Gates phase RESEARCH.md Phase 0 research synthesizes the cross-platform CI matrix, frozen omnivoice_data fixture, installer post-build smoke, SHA-256 checksum publishing, and PR-template extension into copy-paste-ready YAML and Python snippets composed entirely from existing in-repo patterns. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(phase-0): add Gates phase CONTEXT, PATTERNS, and PLAN Phase 0 — Gates is the hard pre-condition for v0.3.x stabilization. Lays cross-platform CI matrix (macos-14/windows-2022/ubuntu-22.04), regression fixture (≤200 KB), installer smoke on tag push, SHA-256 checksums in release body + per-OS SHA256SUMS-*.txt assets, PR template with RC cadence + fixture line, and the open-PR landing for #51. Plan covers GATE-01..06; structured into 7 slices (A–G) with explicit Slice C → Slice G dependency reordering so the new smoke-matrix lands on main before PR #51 (CONTEXT.md L86 interleave decision). Plan-checker iteration 2: APPROVED — all 3 BLOCKERs + 3 MAJORs from iteration 1 resolved (file truncation/Slice-G missing, GATE-06 sibling PR verification, Slice C ordering, Truth #5 wording, macOS Tauri WebView avoidance per Pitfall #5, Windows taskkill per Pitfall #2). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(00-gates): seed regression fixture (GATE-01) - scripts/seed-test-fixture.py — deterministic builder for tests/fixtures/omnivoice_data/ - wipes + rebuilds; fixed created_at=1700000000.0; all-zero PCM for byte-deterministic diffs - calls backend.core.db.init_db() directly (alembic versions/ is empty — see CONTEXT.md) - checkpoints WAL → DELETE on close so no -shm/-wal sidecars pollute git status - exits non-zero if fixture > 200 KB - tests/fixtures/omnivoice_data/{omnivoice.db, README.md} — 8-table empty DB + 1 voice_profiles row - tests/fixtures/omnivoice_data/voices/test-voice/{profile.json, sample.wav} — 1-sec 24 kHz mono silence - .gitignore — explicit allow-list (!tests/fixtures/omnivoice_data/**) so the existing omnivoice_data/, *.db, *.wav patterns don't hide the fixture from git Verifies: du = 144 KB on disk; sqlite_master lists 8 init_db tables + sqlite_sequence; voice_profiles has exactly 1 row id='test-voice'; 0 rows in generation_history. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(00-gates): add tests/smoke/test_boot_smoke.py (GATE-01) - tests/smoke/__init__.py — package marker so pytest treats tests/smoke/ as a module - tests/smoke/test_boot_smoke.py — 4 in-process FastAPI TestClient smoke tests: * test_health_returns_ok — /health returns 200 + {status:ok, device:...} * test_profiles_endpoint_lists_fixture_voice — /profiles surfaces the seeded test-voice row (validates OMNIVOICE_DATA_DIR wiring → DB_PATH → init_db schema) * test_system_info_includes_data_dir — /system/info resolves data_dir * test_history_endpoint_empty — /history reaches DB and returns [] Test isolation env vars (OMNIVOICE_MODEL=test, OMNIVOICE_DISABLE_FILE_LOG=1) set at module top BEFORE any backend import — pattern from tests/test_router_smoke.py. Fixture is copied to a per-session temp dir so the test never mutates the checked-in artifact (SQLite file-change counter + runtime subdirs like dub_jobs/ would otherwise dirty `git status` after every run). Failure mode: if tests/fixtures/omnivoice_data/ is missing, pytest.fail at import time with the regenerate command. - .gitignore — tighten the GATE-01 allow-list to ONLY the seed-produced files (README.md, omnivoice.db, voices/test-voice/profile.json, sample.wav). Prevents future runtime subdirs the backend may create under the fixture from being accidentally committed. Verifies: `uv run pytest tests/smoke/ -q --tb=short` → 4 passed in 1.31 s (target was < 30 s). `git status` clean after a test run. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(triage): record post-planning GitHub state — PR #62, new issues, OOS deferrals - GATE-06: mark #53 + #61 merged (2026-05-16); add #62 (Wave 1 quick wins) to gate set - INST-01: note PR #62 implements setuptools pin (closes #58) - INST-04: note PR #62 lands README docs for #56 workaround - INST-12: new requirement for #65 Windows Triton/torch.compile OOM (filed post-planning) - Out of Scope: defer #67/PR #68 (audio effects), #64 (custom model dir), PR #66 zh-CN (i18n milestone), #63 (empty-template bug) PR #62 is the user's own Wave 1 work landed as a separate PR while GSD planning ran in parallel. Merging it eliminates duplicate work in Phase 1. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(00-gates): add cross-platform smoke matrix (GATE-02) - New smoke-matrix job on macos-14, windows-2022, ubuntu-22.04 - needs: test, fail-fast: false, timeout-minutes: 10 - Pinned actions: checkout@v4, setup-python@v5, setup-uv@v3 (cache enabled) - Per-OS ffmpeg + libsndfile install (brew/choco/apt via awalsh128 cache) - UV_HTTP_TIMEOUT=120, UV_HTTP_RETRIES=5 for restricted-network resilience - Narrow scope: uv run pytest tests/smoke/ -q --tb=short - Existing `test` and `tauri-cross-platform` jobs untouched Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: add workflow_dispatch to ci.yml so smoke-matrix can run on feature branches * feat(00-gates): add --health-check CLI flag to backend entrypoint (GATE-03) - argparse on __main__ block; --health-check boots uvicorn in a daemon thread and polls http://127.0.0.1:3900/health every 5s for up to 60s. - Prints 'OK — /health responded 200 after Ns' and exits 0 on first 200. - Prints 'FAIL — /health did not respond 200 within 60s' to stderr and exits 1 on timeout. Default invocation behavior unchanged. - No new deps (stdlib argparse/threading/time/urllib.request/sys + uvicorn). - Consumed by per-OS installer-smoke step in .github/workflows/release.yml. Verified locally: exits 0 in 5s against tests/fixtures/omnivoice_data/. * ci(00-gates): add per-OS installer smoke to release.yml (GATE-03) Adds three matrix-leg-specific steps after 'Build + release (Tauri)', each gated by runner.os with timeout-minutes: 5: - macOS (macos-14): hdiutil attach DMG → locate bundled Python backend inside *.app/Contents (NOT the Tauri WebView shell — RESEARCH Pitfall #5: WebView hangs on headless runners) → invoke --health-check → hdiutil detach. Falls back to *.app/Contents/Resources and hard-fails with a directory listing if no backend binary found. - Windows (windows-2022): msiexec /quiet install → find backend.exe under 'C:/Program Files/OmniVoice Studio' → invoke --health-check in background, wait, then taskkill //F //T //PID to cleanup orphaned PyInstaller child processes on port 3900 (RESEARCH Pitfall #2). - Linux (ubuntu-22.04): --appimage-extract (no FUSE on GH runners), locate binary or AppRun, run under xvfb-run -a. Bundle-only regressions (PyInstaller missing-module, Tauri sidecar path mismatch) are invisible to ci.yml's in-process smoke matrix — this step closes that gap before any release is published. Verified: YAML parses; all three steps present; gating + timeout correct; Pitfall #2/#5 mitigations preserved. * ci(00-gates): publish SHA-256 checksums in release body + as asset (GATE-05) - Add 'Compute SHA-256 checksums' step writing SHA256SUMS-<label>.txt per matrix leg using native shasum/sha256sum (Git Bash on Windows). - Add 'Append checksums to release + attach SHA256SUMS file' step using softprops/action-gh-release@v2 with append_body: true so the hashes land in the release body alongside tauri-action's content (not replacing it) and the file is uploaded as a release asset for 'shasum -c SHA256SUMS-<label>.txt' verification. - Both steps gated by 'github.event_name == push && refs/tags/v*' so workflow_dispatch dry-runs do not attempt to attach to a non-existent release (per CONTEXT.md L70 + RESEARCH Pitfall #7 deferral of any aggregate cross-leg SHA256SUMS job). - fail_on_unmatched_files: true to surface path-resolution errors loudly. * docs(00-gates): document RC cadence + regression-fixture check in PR template (GATE-04) * docs(setup): add HF token persistence guide for macOS/Windows/Linux (DOCS-05) Covers two persistent paths: - Method A — canonical ~/.cache/huggingface/token via huggingface-cli login - Method B — shell env var (~/.zshrc / ~/.bashrc / Windows User scope) Documents the v0.2.7 "session only" in-app behavior + notes that Phase 1 AUTH-03 will make in-app pastes write to the canonical file. Bundled with Phase 0 PR per user request. Strictly DOCS-05 scope — zero code changes, no engine touches. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * spec(auth): redesign HF token resolution as 3-source cascade with fallback (AUTH-01..06) Replaces the env_store.py file-based design with a SQLite-backed app store + cascade resolver that checks app → env var → ~/.cache/huggingface/token in priority order, with automatic fallback to next source on HTTP 401. User-explicit design decision: - App-stored token (SQLite settings table, AES-GCM encrypted) wins - Env var ($HF_TOKEN) second - Global huggingface-cli login file third - All three sources visible in Settings → API Keys with "Active" badge - Save action populates BOTH app store AND canonical HF file (defense in depth) New requirement: - AUTH-06 — on 401, auto-retry next source in cascade before erroring Also: traceability count corrected (62 → 74 — undercount at planning + INST-12 + AUTH-06 added post-planning). All 74 v1 reqs mapped. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(auth): backend recognizes HF token from canonical file, not just env var Two call sites were only checking $HF_TOKEN env var, missing the canonical ~/.cache/huggingface/token file written by `huggingface-cli login` (or the app's future Save action): - system.py `/system/info` `has_hf_token` flag — UI showed "No HF token" even when `huggingface-cli login` had populated the file. - model_manager.get_diarization_pipeline — pyannote diarization silently returned None when only the canonical file was set. This is the bug behind issue #35 (speaker diarization setup failure). Both fixes use the same pattern: env var > huggingface_hub.get_token() (which reads the canonical file). Adds a local _has_hf_token() helper to system.py with a comment marking it as prelude to the AUTH-01..06 cascade (Phase 1 token_resolver.py will layer SQLite app-store on top). Closes #35 sub-issue (canonical token invisible to diarization). Cross-cuts AUTH-02 + AUTH-06 design for Phase 1. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(dictation): make pill-widget mode reachable from GUI + scripts (INST-13) The dictation widget infrastructure shipped in PR #40 but was only reachable via the undocumented --pill CLI flag. Adds three discovery paths: 1. Tray menu: "Switch to Dictation Widget" (studio mode) — saves launch_as_widget=true to config, relaunches with --pill, exits current. Mirrors the existing "Open Studio" path in pill-mode tray. 2. Persistent config: AppConfig.launch_as_widget (bool, default false). Read at startup via load_config_pre_app() (uses dirs-next, no AppHandle required). CLI --pill still takes precedence when explicitly passed. 3. Tauri commands: get_launch_as_widget / set_launch_as_widget for the Phase 2 Settings UI to bind a checkbox to. 4. Scripts: bun desktop-prod:pill / desktop-prod:run:pill — forward --pill to the bundled app launch. macOS uses `open -n --args` to spawn fresh instance with the flag. Closes the GUI half of INST-13. Phase 2 closes the Settings UI half. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(dictation): show widget unconditionally on pill-mode launch + visible Suspense fallback Before: pill mode set up correctly but the widget window stayed hidden until ⌘⇧Space was pressed. New users saw absolutely nothing on launch (no main window, no dock icon, hidden widget) and assumed the app failed. If global-shortcut Accessibility permission wasn't granted, they had no path to discover the widget at all. Two changes: 1. lib.rs: in pill_mode_setup, explicitly show + position + focus the widget window after hiding main. With per-call error logging so we can diagnose failures (and a clear error log if widget window wasn't created at all — points at tauri.conf.json regression). 2. main-app.jsx: Suspense fallback was `null`, which combined with widget's transparent+decorations:false config made any lazy-import delay or failure invisible. Now renders a dark pill saying "Loading dictation…" so even if CaptureWidget lazy-import stalls, the user sees the window exists. Studio mode behavior unchanged — widget stays hidden until hotkey or tray click triggers it (existing show() call in the shortcut/ menu handlers is preserved). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(dictation): create widget window programmatically; Tauri 2 silently dropped config-array creation Root cause: declaring the widget window in tauri.conf.json's app.windows[] silently failed in Tauri 2 — get_webview_window("widget") returned None even though the config was syntactically valid. Probable culprit was the transparent + decorations:false + visible:false combo, but Tauri offered no error message either at startup or via webview_windows() enumeration. Diagnosed by adding webview_windows() enumeration logging at setup start (only ["main"] ever appeared) and a programmatic WebviewWindowBuilder fallback that surfaces real Result errors. Fix: - tauri.conf.json: widget entry now has `create: false` to make the config-vs-programmatic handoff explicit. - lib.rs setup(): call WebviewWindowBuilder::new(app, "widget", ...).build() with the exact same surface attributes the config used to declare. - capabilities/default.json: include "widget" in windows array so the new window inherits the same Tauri permissions as main. - tauri.conf.json: remove the invalid `"url": "/?window=widget"` field — WebviewUrl::App takes a path only, query strings aren't supported. Both windows now load index.html. - main-app.jsx: replace URL-query-based widget detection with getCurrentWindow().label === 'widget' via @tauri-apps/api/window. This is the Tauri 2-recommended pattern for multi-window apps and works regardless of URL routing. Closes the immediate UX bug behind the dictation widget being invisible. Builds cleanly + manually verified: pill widget visible on screen at top-center after `bun desktop-prod:pill`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
9082705676 |
Post-refactor cleanup: wire fingerprints, drop dead code, scope pytest (#50)
* chore: post-refactor cleanup — wire fingerprints, drop dead code, scope pytest Follow-up to PR #49. Fixes residual issues from the App.jsx hooks split and tightens repo hygiene so a bare `pytest` doesn't foot-gun. Real bug - frontend/src/hooks/useDubWorkflow.js: setLastGenFingerprints lives in useSegmentEditing, not on the store. The previous code called useAppStore.getState().setLastGenFingerprints?.(...) — the optional chain swallowed the missing method, so the "N segments changed" badge never updated after a fresh generate until a project save+reopen. Thread setLastGenFingerprints in from App.jsx; useSegmentEditing() now runs before useDubWorkflow() to make the setter available. Dead code from the refactor - frontend/src/App.jsx: drop unused `showAllProjects` useState and `pushUndo` from the useSegmentEditing destructure. - frontend/src/hooks/useDubWorkflow.js: drop 5 unused selectors (preserveBg, defaultTrack, exportTracks, dualSubs, burnSubs) — the dub-download logic that needs these lives in App.jsx, not the hook. Repo hygiene - backend/api/routers/setup.py.bak: delete 38 KB tracked-in-git backup. The setup/ subpackage replacement has been in place for a while. - pyproject.toml: add [tool.pytest.ini_options] with testpaths + norecursedirs. Previously a bare `pytest` would INTERNALERROR walking into research/ (1.2 GB of vendored upstream projects with their own test_*.py files that call sys.exit at module level). - .github/workflows/ci.yml: run backend/tests/ as a second pytest invocation. The 23 tests there stub core.config in sys.modules to avoid the heavy main app import chain — that pollutes import state for other tests, so they need their own session. Previously these tests existed in the repo but never ran on CI. Net effect on lint: 60 → 52 problems (-8) from dead-code removal. Test counts unchanged: pytest 214 + 23, vitest 11. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: log silent catch failures that mask real bugs CodeRabbit nitpick on #50: empty catch on the incremental-plan fallback swallows errors. Extending the fix to the catches in this area that have the same problem (a real failure would be invisible) while leaving the genuinely non-actionable cleanup catches alone (EventSource.close(), localStorage.setItem, fire-and-forget UI promises). Logged: - useDubWorkflow.js:97 — transcribe SSE message handler - useDubWorkflow.js:347 — incremental-plan fallback (the CR finding) - useDubWorkflow.js:352 — dub generate SSE event dispatch - App.jsx:552 — exportRecord on Tauri save path - App.jsx:580 — exportRecord on browser download path Left silent (cleanup / non-actionable): - useDubWorkflow.js:68, 112 — evt.close() in SSE teardown - useDubWorkflow.js:102 — SSE error-event payload parse fallback - App.jsx:124 — localStorage.setItem (quota / privacy mode) - App.jsx:789, 901 — fire-and-forget UI promise tails Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
a1ef66c321 |
Stability pass: DB leaks, App.jsx hooks refactor, desktop bootstrap (#49)
* fix: eliminate DB connection leaks, race conditions, and deprecated asyncio API ## DB Connection Leaks (P0) - Convert 38 raw get_db() calls to db_conn() context manager across 14 router files - Connections are now guaranteed to close even when exceptions are raised - profiles.py create_profile: clean up orphaned audio file if DB insert fails - profiles.py lock_profile: consolidate 3 separate conn.close() error paths ## Race Condition (P1) - Add _dub_jobs_lock (threading.Lock) to protect _dub_jobs dict in dub_pipeline.py - get_job/put_job now thread-safe for concurrent dub sessions ## asyncio Deprecation (P2) - Replace 23 asyncio.get_event_loop() calls with asyncio.get_running_loop() - Prevents DeprecationWarning on Python 3.12+ and future breakage on 3.14 ## Quick Fixes - gallery.py preview_voice: remove filesystem path from error response (P2) - dub_pipeline.py parse_vtt_segments: remove redundant `import re` inside loop (P3) - gallery.py _init_gallery_db: use db_conn() context manager (P2) * refactor: extract hooks, centralize isTauri, add pytest-cov ## Frontend - Extract useTTS hook (150 LOC) — TTS generation, streaming, audio ingestion - Extract useProfiles hook (219 LOC) — voice profile CRUD, lock/unlock, preview - Centralize isTauri detection: dialog.js, VoiceGallery.jsx, Settings.jsx now import from utils/media.js instead of 4 different detection patterns ## Backend - Add pytest-cov to dev dependencies - Baseline coverage: 39% across backend/ (214 tests pass) - Add .coverage to .gitignore * feat: add Vitest + checkJs, extract useDubWorkflow + useAppData hooks ## Frontend Testing (new) - Set up Vitest with jsdom environment + @testing-library/react - 11 tests: utils (isTauri, formatTime, constants) + Zustand store (mode, text, dubStep, pill) - Scripts: 'test' (vitest run), 'test:watch' (vitest), 'test:legacy' (node runner) ## App.jsx Decomposition (continued) - Extract useDubWorkflow hook (387 LOC) — upload, ingest, transcribe SSE, translate, generate SSE, abort, stop, cleanup - Extract useAppData hook (181 LOC) — data loading, localStorage persistence, WebSocket real-time updates, model-status pill management ## TypeScript checkJs - Enable checkJs: true in tsconfig.json for IDE-level type checking - 947 existing errors (informational, not blocking builds) - noImplicitAny remains false to avoid blocking * ci: add Vitest step, fix useProfiles duplicate state ## CI - Add 'Run Vitest (frontend)' step — runs 11 unit tests - Override --checkJs false in CI typecheck to avoid 947 pre-existing errors - Rename legacy test step for clarity ## Hooks - Fix useProfiles to accept loadProfiles from parent (useAppData) instead of managing its own duplicate profiles array * refactor: wire hooks into App.jsx — 2067 → 1129 LOC (-45%) App.jsx now delegates to extracted hooks instead of inline logic: - useAppData: data loading, localStorage, WebSocket, model pill - useProfiles: voice profile CRUD, lock/unlock, preview - useTTS: generation, streaming, audio ingestion - useDubWorkflow: upload, transcribe SSE, translate, generate SSE 988 lines removed. All handler logic lives in focused, independently testable hooks. Store selectors and render JSX stay in App.jsx as the shell. Verified: vite build clean, 11 frontend + 214 backend tests pass. * feat: show real-time percentage on model loading pill Backend: register hf_progress listener during _load_model_sync() so download/weight-loading tqdm events update _loading_detail with a progress percentage (0-99%). get_model_status() now includes a 'progress' field that the frontend polls. Frontend: useAppData reads msQuery.data.progress and calls setPillProgress() — the FloatingPill already renders the percentage text and progress bar width from this value. * fix: prevent FileNotFoundError in desktop bundle during model init transformers >=4.52 calls _can_set_experts_implementation() and _can_set_attn_implementation() during PreTrainedModel.__init__, which open the class source file via open(class_file). In a Tauri desktop bundle, module.__file__ points to a path that doesn't exist on disk, causing: FileNotFoundError: .../omnivoice/models/omnivoice.py Override both classmethods on OmniVoice to return static values without filesystem access. OmniVoice doesn't use MoE experts (return False), but does support flex/flash attn (return True). * fix: sync source dirs on every bootstrap, not just first run The Tauri bootstrap previously only copied omnivoice/ and backend/ to Application Support on the first run. Subsequent app updates kept using stale source files, preventing bug fixes from landing. Now ensure_venv_ready() always syncs both directories from the bundle resources before returning, even when the venv is healthy. This fixes the FileNotFoundError crash where the old omnivoice.py lacked the _can_set_experts_implementation override. * ui: premium setup wizard polish - Primary button: solid gradient fill with hover glow + lift + press - Stepper nav: connected pills with glow ring on active step - Welcome cards: glassmorphism with stagger-in animations, lucide icons, left-border accent strip, hover translate - Preflight panel: colored icon pill backgrounds, stagger-slide entrance - Step transitions: fade+slide animation via keyed wrapper - Footnote: shortened paths (~/ notation), Reveal in Finder button - Recommendation banner: gradient background with accent glow - Compact spacing throughout for denser, professional layout * fix: kill zombie backend on clean+retry bootstrap When clean_and_retry_bootstrap removes the project dir, any old uvicorn process still running from the deleted paths remains alive on port 3900. The subsequent retry_bootstrap sees the port is healthy and attaches to the zombie instead of re-bootstrapping. Now explicitly kill any process on the backend port after cleaning, before calling retry_bootstrap. * feat: integrate speaker clones into dubbing interface, sanitize system environment variables for subprocesses, and improve FFMPEG binary path resolution. * fix: restore docker compose default + drop dead setSeed call - deploy/docker-compose.yml: remove profiles: ["cpu"] from the default service so `docker compose up` matches the comment on line 5. With the profile present, no service auto-started. - frontend/src/App.jsx: drop the setSeed call in restoreHistory. The selector was never reintroduced after the App.jsx hooks split, and there is no seed state in the store — seeds are generated fresh per call in useTTS and only read from history items for display. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: address CodeRabbit review — async detection, dub stream, bootstrap fail-fast - backend/services/tts_backend.py: invert async-context detection in _ensure_loaded. The previous code unconditionally caught its own diagnostic RuntimeError and then called asyncio.run() inside a running loop, masking the intended error message. - frontend/src/hooks/useDubWorkflow.js: require a terminal `done` event before reporting dub success. Without this, a dropped stream after partial progress would flip the UI to `done`, refresh history, and play the completion ping as if generation finished. - frontend/src/hooks/useDubWorkflow.js: restore the previous step when tasksCancel() fails. The UI was getting stuck in `stopping` forever on cancel errors. - frontend/src-tauri/src/bootstrap.rs: fail-fast when source sync fails after the existing directory has already been removed. The previous warn-and-continue path could leave the install with no backend/ or omnivoice/ sources and defer the failure to backend startup with a cryptic error. - backend/api/routers/generation.py: add `from e` to the ValueError → HTTPException re-raise (Ruff B904). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: preserve % suffix in TTS generation timer The 100ms timer in useTTS was rewriting generationTime to a plain elapsed-seconds string, which immediately wiped the "(xx%)" download suffix written on the next iteration of the response-body loop. The real-time percentage was flickering on/off as a result. Read the previous value inside the setter and reattach any existing percent suffix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
545b39c912 |
feat: Scalar API docs, community health files, Quickstart cards (#41)
* feat: Scalar API docs, community health files, Quickstart cards, GHCR Docker Backend: - Replace Swagger UI with Scalar at /docs (scalar-fastapi) - Add OpenAI-compatible /v1/audio endpoints (openai_compat router) - Add TTS streaming endpoint (tts_stream router) - Add voice marketplace router (marketplace) - Update TTS backend registry Frontend: - Refine CaptureWidget, WaveformTimeline, App layout - CSS polish and index.css updates Community health: - SECURITY.md — vulnerability reporting policy - CODE_OF_CONDUCT.md — Contributor Covenant v2.1 - .github/FUNDING.yml — GitHub Sponsors - .github/ISSUE_TEMPLATE/ — bug report + feature request - .github/pull_request_template.md — PR checklist README: - Quickstart redesigned as 3-column progressive cards - Docker section updated with GHCR pull instructions - API Docs row added to service table Infra: - scalar-fastapi added to pyproject.toml + uv.lock - research/ added to .gitignore * refactor: clean up documentation and logging while enhancing desktop packaging dependencies and capture UI performance. * fix: address CodeRabbit review — streaming, escaping, thresholds Backend: - marketplace: stream zip entries via ZipFile.open()/copyfileobj, add 100MB upload cap, fix raise-from exception chaining (OOM prevention) - openai_compat: _encode_audio returns actual file ext so Content-Disposition matches real format; forward non-profile voices when DB row not found - tts_stream: send 'start' frame after generation so sample_rate is real; forward non-profile voices on DB miss - capture_ws: split MIN_BUFFER_BYTES into separate partial/final thresholds so short utterances (<2s) still get transcribed Frontend (Tauri): - lib.rs: tray 'dictate' now toggles start/stop based on widget visibility - commands.rs: XML-escape exe path in LaunchAgent plist, shell-quote in .desktop Exec line to prevent injection from special-char paths - CaptureWidget.css: fix Stylelint violations (empty lines, font-family quotes) |
||
|
|
cfab2a500a |
feat: add GHCR Docker workflow, update README with container registry instructions
- New .github/workflows/docker.yml publishes images to ghcr.io on tag push - README Docker section now leads with 'docker pull' from GHCR - docker-compose.yml defaults to GHCR image with build-from-source fallback - Dockerfile: copy README.md for hatchling metadata resolution |
||
|
|
37a03acae3 |
feat: implement frameless OS-level floating dictation widget
- Refactor CaptureButton into standalone CaptureWidget - Add secondary transparent Tauri window configuration - Map global hotkey to show/hide widget instead of focusing main app - Implement auto-hide post-paste - Add social preview image |
||
|
|
41c23f6b3a | feat: enhance ASR performance and reliability with binary bundling, model warmup, sub-stage progress tracking, and optimized polling. | ||
|
|
c8d1858420 | refactor: bundle uv binary per-platform as Tauri sidecar and remove redundant ffmpeg bootstrap download | ||
|
|
d6b1dc1b49 |
fix(0.2.6): WS first-chunk drop, mic permissions, release-body from CHANGELOG
WS dictation pipeline was producing exit-183 from ffmpeg on every
partial because MediaRecorder.start(250) ran before the WebSocket
handshake finished — the first chunk (WebM EBML header) was queued
only into chunksRef and never pushed to the WS, so concatenated
chunks 1..N decoded as malformed WebM. Fix:
- Construct the WebSocket BEFORE starting the recorder so wsRef is
set when the first ondataavailable fires.
- ondataavailable now queues every chunk through wsPendingRef when
the socket isn't OPEN; ws.onopen drains the queue.
- ws.onmessage('error'): fire HTTP fallback immediately instead of
waiting the full fallback-timeout window.
- ws.onclose without prior `final`: same — kick the HTTP path now
if the recorder has already stopped.
Mic permissions:
- New frontend/src-tauri/Info.plist with NSMicrophoneUsageDescription
+ NSCameraUsageDescription. Tauri 2 auto-merges the file at bundle
time (path is the same dir as tauri.conf.json — schema documents
this fallback). Without it, getUserMedia silently fails on macOS
10.14+ TCC.
- Mic-denial toast now includes platform-specific recovery (Settings
paths for macOS/Windows, audio-group check for Linux).
CI / release notes:
- release.yml extracts the matching `## [X.Y.Z]` section from
CHANGELOG.md and feeds it into tauri-action's releaseBody, so
v0.2.6+ tag pushes produce real release notes instead of the
placeholder "Auto-generated release. See commit log for changes."
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
79d4f3b53d |
feat(0.2.6): tray-aware shell, hotkey customization, WS dictation dedupe
Tray + lifecycle: - tauri-plugin-single-instance — second launch focuses existing window instead of racing for port 3900. - Window close hides instead of destroying; backend shutdown moved to RunEvent::ExitRequested so only the tray "Quit" item (or Cmd+Q on macOS) actually exits. - Tray icon flips to red-dot variant during dictation recording. Hotkey customization: - Settings → Capture tab. Records any modifier+key combo, persists to app config, re-registers on launch. - set_dictation_shortcut rolls back to the previous binding on register failure so a bad combo never leaves the user with no shortcut. Dictation latency / correctness: - WS-final treated as source of truth; HTTP POST /transcribe runs only as fallback (WS error / timeout / no-WS path). Audio transcribed once instead of twice. Server accepts an "EOF" text frame (or empty binary frame) so the socket stays open for `final` to be delivered before the client closes. - MediaRecorder chunks queued during the WS handshake are drained in ws.onopen — the server's final transcript no longer drops the first ~250 ms of audio. - Fallback timeout scales with recording length (max(15s, recordedMs+10s)) so long-form dictations don't trip duplicate transcription. Donate page: - Drop Patreon, Bitcoin / Ethereum / Solana cards. Drop qrcode.react. - Move "Commercial License" CTA from page bottom to top-right header bar. Docker hygiene: - docker-compose binds 127.0.0.1 by default. README documents the LAN exposure trade-off + recommends a reverse proxy with auth. CI: - New cross-platform `tauri-cross-platform` job runs `cargo check` against the Tauri shell on macOS / Windows / Linux per PR. Catches platform cfg-gate regressions without paying the full ~15min/platform bundle cost (full bundling stays in release.yml on tag push). Tests: - tests/test_capture_ws.py (3 cases) covers EOF text-frame, empty-binary EOF, and legacy disconnect-finalize paths. Includes the user's previously-staged 0.2.5 polish: cross-platform desktop-prod.sh, Dockerfile base-image fix, bun.lock churn. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
8c27ba40dc |
feat(release): add Linux AppImage bundle (#23)
AppImage was dropped earlier when linuxdeploy's AppImage runtime couldn't FUSE-mount on GH Actions runners. Now viable again because: 1. `APPIMAGE_EXTRACT_AND_RUN=1` bypasses FUSE (extract-and-run). 2. The thin uv-venv installer is ~10 MB (vs the prior ~2 GB PyInstaller payload that tripped linuxdeploy's internal size limits). Matrix `bundles` for Linux: `deb,updater` → `deb,appimage,updater`. tauri.conf.json `targets` also updated so dev builds can produce AppImages locally. Covers universal Linux — runs on any glibc-2.31+ host without a package manager. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
5a754b461e |
chore(release): drop macOS Intel from matrix (#21)
Apple shipped the last Intel Mac in June 2023 and Rosetta 2 runs the ARM build natively at 85-100% of native speed. macos-13 runner backlog was also blocking every v0.2.0 retag for ~10 min waiting on a hosted Intel runner — measurable pain for no measurable user reach. If we ever need Intel builds back, the matrix entry is one block of five YAML lines. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
90ef02b2e4 |
chore: unique ports (3900/3901) + broader CI caches (#20)
Two unrelated tweaks grouped into one PR to keep churn low. ## Ports Backend 8000 → 3900, Vite dev 5173 → 3901, 3902 reserved for future IPC. Port 8000 conflicts with Django/Rails/Jupyter/Airflow on most dev machines; the uncommon 3900 range dodges that. Touched: - frontend/src-tauri/src/lib.rs (BACKEND_PORT) - frontend/src-tauri/tauri.conf.json (devUrl) - frontend/vite.config.js (server.port) - frontend/src/api/client.ts (hardcoded API base) - frontend/src/App.jsx (PREVIEW_API fallback) - backend/main.py (CORS allowlist + uvicorn.run default) Rust sidecar launcher and FastAPI uvicorn port stay in sync via the `BACKEND_PORT` constant + explicit port=3900. ## CI caches Build time shaves across ci.yml and release.yml: - `astral-sh/setup-uv@v3` → `enable-cache: true` keyed on uv.lock (~45 s saved per run after uv.lock stabilises) - `awalsh128/cache-apt-pkgs-action` for ffmpeg (~25 s saved) - `actions/cache@v4` on `~/.bun/install/cache` keyed on bun.lock (~15 s saved; applied to both test gate and build matrix) Expected warm test job: ~45-60 s (was ~2-3 min). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
e354d27c56 |
chore(frontend): bump deps + TypeScript 6 (#18)
* ci: cache Rust deps for Tauri build (~5 min → ~1-2 min on warm runs) Cargo dep compile is the long pole of each Tauri build now that PyInstaller is out. Add Swatinem/rust-cache@v2 keyed by rust_target so each matrix job (mac arm, mac intel, windows, linux) gets its own cache. Caches ~/.cargo/registry + frontend/src-tauri/target. Expected: cold first run stays ~5-7 min per platform; subsequent runs on the same rust_target drop to ~1-2 min. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(frontend): bump deps + TypeScript 6 - vite 8.0.8 → 8.0.9 (patch) - eslint 10.2.0 → 10.2.1 (patch) - eslint-plugin-react-hooks 7.0.1 → 7.1.1 - globals 17.4.0 → 17.5.0 - typescript 5.9.3 → 6.0.3 (major) TS 6 warns on tsconfig's `baseUrl` ("deprecated, removed in 7.0"); add `ignoreDeprecations: "6.0"` to keep the current path-alias setup until we migrate off baseUrl in the next cycle. Verified locally: `tsc --noEmit` clean, `vite build` produces bundles identical in shape to the prior version. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
865897aeb7 |
feat(release): replace PyInstaller with uv-venv bootstrap, thin installers (#16)
Supersedes the PyInstaller tarball approach from PR #15. PyInstaller never made it past iteration — even with CPU-only torch + strip + optimize, the Linux .deb and Windows MSI both overshot GH Releases' 2 GB per-asset cap. Trying to keep it under the cap also meant CUDA was off the table for users who did have a GPU. Switch to the bootstrap pattern Unsloth uses: - Installer ships the Tauri shell + frontend dist + repo's pyproject.toml + uv.lock + backend/ source tree as Tauri resources. DMG is 8.9 MB (verified locally). MSI / .deb should be similar. - On first launch, src-tauri/src/lib.rs::ensure_venv_ready() downloads the standalone `uv` binary (if not already on PATH), copies the bundled pyproject.toml + uv.lock + backend/ into `app_local_data_dir/project`, then runs `uv venv --python 3.11` + `uv sync --frozen --no-dev`. Subsequent launches skip. - spawn_backend launches `{venv_python} -m uvicorn main:app --app-dir {project/backend}` — no more PyInstaller binary. - Dev mode still wins: if `.venv` at the source tree exists, reuse it (matches `bun run dev` behaviour). Release workflow drops: Setup Python, Install uv, CPU-torch reinstall, PyInstaller freeze, backend tarball package, and backend tarball upload steps. CI now just builds Rust + bundles resources; the heavy deps install happens once on each user's machine. User impact: - Tiny installer → instant download + install (no 300–700 MB tarball). - First launch: ~5–10 min setup while uv materialises the venv. This happens behind the initial webview splash; subsequent launches are normal. - Users get the right torch wheel for their box — CPU by default, CUDA if they already have the drivers (uv resolves from pyproject). - Updates: bumping deps = bump uv.lock + ship a new installer; no PyInstaller rebuild needed. Known follow-ups: - Progress UI during first-run bootstrap (React splash polling a Tauri command). Right now the webview stays on the loading screen. - Retry / repair flow if bootstrap fails (network drop, etc.). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
c934ba2d7f |
feat(release): split backend out of installer, download + extract on first run (#15)
All three desktop platforms previously built a single asset — installer + PyInstaller backend bundled together — that overshot GH Releases' 2 GB per-asset cap on Linux and Windows. The mac DMG got under the cap thanks to HFS compression, but Linux .deb and Windows MSI couldn't. NSIS and WiX both failed during their own size-bounded packaging steps too. Split the two: - Tauri installer ships WITHOUT the PyInstaller backend (`tauri.conf.json` bundle.resources is now empty). Installer sizes drop from ~1.8 GB to ~50 MB. - CI packages the frozen backend as `omnivoice-backend_<version>_<triple>.tar.gz` after tauri-action, and uploads it to the same draft release via `gh release upload`. Each tarball is gz-compressed + comfortably under 2 GB with the CPU-only torch wheel + strip=True from earlier PRs. - On first launch, `ensure_backend_ready()` checks three locations in order: resource dir (legacy), app_local_data_dir (new home for the downloaded backend), and the dev-mode `dist/` fallback. If none match, it downloads the tarball matching the current platform + app version from the GH Release and extracts into app_local_data_dir. Blocking on first run, no-op thereafter. - find_bundled_backend + backend_exe_name are platform-aware — they append .exe on Windows and scan all three roots. Dependencies added to src-tauri/Cargo.toml: ureq (HTTP), tar + flate2 (archive extract). No tokio — ureq is synchronous, which matches the existing setup() flow. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d29db18214 |
fix(release): strip+filter bundle + report size (#13)
* ci: opt JavaScript actions into Node 24 runtime GH deprecates Node 20 for JavaScript actions on 2026-09-16. The deprecation warning surfaces on every run right now. Setting FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true at workflow level makes actions/checkout, actions/setup-*, astral-sh/setup-uv, and oven-sh/setup-bun all run on Node 24 without bumping action versions. This is a runtime override only — our own test script still pins Node 22 via actions/setup-node@v4 (required for --experimental-strip-types). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(release): strip symbols + filter CUDA/CUDA-provider binaries, report size Previous slim pass (PR #11, CPU-only torch + module excludes) still left the frozen backend above GH Releases' 2 GB per-asset cap. Two more levers: 1. strip=True on EXE + COLLECT. Strips debug symbols from ELF/Mach-O native libraries. libtorch_cpu.so and friends drop ~25-30%. No-op on Windows (MSVC stores symbols in separate .pdb files). 2. optimize=2 in Analysis. Compiles embedded bytecode with -OO: docstrings + assertions removed. ~50-80 MB off the PYZ archive. 3. Post-hoc binary filter after collect_all. Even with nvidia wheels excluded as Python modules, collect_all('torch')/('onnxruntime') can still pull the CUDA-runtime shared libs via their linker hints. Pattern-match them out of a.binaries before PYZ. 4. Log bundle size after freeze so CI runs can be compared without downloading artifacts. If this round still overshoots 2 GB, the next step is splitting the payload (thin installer + post-install download of the Python bundle). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
c3aa2e4533 |
ci: opt JavaScript actions into Node 24 runtime (#12)
GH deprecates Node 20 for JavaScript actions on 2026-09-16. The deprecation warning surfaces on every run right now. Setting FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true at workflow level makes actions/checkout, actions/setup-*, astral-sh/setup-uv, and oven-sh/setup-bun all run on Node 24 without bumping action versions. This is a runtime override only — our own test script still pins Node 22 via actions/setup-node@v4 (required for --experimental-strip-types). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
77fec801ba |
fix(release): slim PyInstaller bundle below GH's 2 GB per-asset limit (#11)
Linux .deb upload and Windows MSI build both hit GitHub Releases' hard 2147483648-byte asset cap because the frozen backend was ~2.2 GB on Linux/Windows. Root causes + fixes: - PyPI's default torch/torchaudio wheels bundle the full CUDA runtime (~1.8 GB of libcuda*, libcublas*, libcudnn*, libcufft*, libcusparse*, etc.). We ship CPU-only inference from the desktop binary; GPU is surfaced only when a user-installed driver is detected at runtime. Re-install torch from download.pytorch.org/whl/cpu for the Linux and Windows matrix jobs before PyInstaller freezes. macOS wheels don't include CUDA so they skip this step. - Expand backend.spec excludes: torch subpackages we never touch at inference time (torch.distributed, torch._dynamo, torch._inductor, torch._export, torch.testing, torch.onnx, torch.ao, torch.fx. experimental, torch._functorch, torch.utils.tensorboard, torch.utils.benchmark), torchaudio.prototype, and heavy pyproject deps the backend never imports (gradio, tensorboardX, webdataset, s3prl, funasr, pedalboard). Also drop test trees that collect_all sweeps up (scipy.special.tests, numpy.f2py.tests, etc.). Expected bundle size after trim: ~600-900 MB uncompressed on Linux / Windows, well under the 2 GB cap for .deb and MSI. Model weights were never bundled — they already download on first run via the HF cache when the user hits the Dub / TTS / ASR flows. So no user-visible behaviour changes; the app just ships without the libs required for CUDA builds, which weren't callable on those runners anyway. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
f79c9b1c70 |
fix(release): force per-platform bundle targets via --bundles CLI (#10)
Prior run (24797827082) showed tauri ignored tauri.conf.json's bundle.targets filter: Windows build still ran makensis (NSIS) despite the config listing only msi. Explicitly pass `--bundles` per platform via tauri-action args: - macOS: app,dmg,updater - Windows: msi,updater (avoids NSIS's 2 GB stub limit) - Linux: deb,updater (drops unreliable AppImage/linuxdeploy step) Also removed `appimage` from tauri.conf.json's targets list to match, keeping config + CLI in sync. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
092f3cff3f |
fix(release): Linux AppImage FUSE bypass + Windows NSIS→MSI (#9)
Linux (ubuntu-22.04 runner) was failing the linuxdeploy step because GH runners disable FUSE. Setting APPIMAGE_EXTRACT_AND_RUN=1 tells AppImages to extract-and-run instead of mounting via FUSE. Windows (windows-2022) was failing makensis with "Internal compiler error #12345: error mmapping file (1843463346, 33554432) is out of range" — NSIS's 32-bit file handling can't build an installer whose payload approaches the 2 GB boundary (the PyInstaller-frozen backend is ~1.7 GB). Switched the Windows bundle target from NSIS to MSI (WiX), which uses cabinet archives that handle larger payloads. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
dd16354a98 |
ci: invoke node directly for frontend tests; add setup-node to release workflow (#7)
`bun run <script>` auto-aliases `node` to `bun` in script bodies, so `bun run test` fails with "node: bad option: --experimental-strip-types" because bun doesn't support that flag. Call node directly from the CI step instead of going through the package.json script. Also add setup-node to release.yml's test gate — it was missing entirely, relying on bun's node shim. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
3c89c8c2d1 |
ci: use node 22 + --experimental-strip-types so node:test can import .ts
CI ubuntu runner shipped node with no TypeScript loader, so
`await import('.../client.ts')` in tests/frontend/apiClient.test.mjs
failed with ERR_UNKNOWN_FILE_EXTENSION. Locally on macOS bun was
handling the extension transparently.
Fix: pin Node 22 via actions/setup-node@v4 (which natively supports
--experimental-strip-types) and pass the flag in the frontend `test`
script. Type annotations in client.ts are stripped at import time,
test bodies stay untouched.
Verified locally with node v24 — 36/36 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
66fb24602b |
ci: add PR-gated test workflow
release.yml only fires on `push: tags: ['v*']` + workflow_dispatch, so the test job it contained never ran on pull requests — PRs landed with no automated test feedback. Split into a dedicated ci.yml that runs backend pytest + frontend node:test + tsc on every pull_request + push to main. release.yml stays tag-only for the heavy 4-platform Tauri matrix build. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
a9071e6e1b |
test: add preflight + bitrate coverage, refresh legacy mocks, wire CI gate
## New coverage
### tests/test_setup_preflight.py (13 tests, 11 pass + 2 skip)
Covers the /setup/preflight endpoint end-to-end:
- Response shape (ok / has_warnings / checks / device)
- Every check has id/label/status/detail/fix
- All 9 core checks present regardless of platform
- Aggregation logic (ok↔any-fail, has_warnings↔any-warn)
- GPU vendor branches:
* Apple Silicon → vendor=apple, backend=mps
* Missing nvidia-smi falls through
* Old NVIDIA driver (520) flags fail + driver-update fix
* AMD with CUDA torch warns with ROCm install instructions
- Network probe handles unreachable host gracefully
- RAM fail threshold (<8 GB) + warn threshold (<12 GB)
Branches not reachable on the current host are skipped with a clear
reason so the suite stays green across mac-ARM / mac-Intel / win / linux.
### tests/test_dub_export_bitrate.py (20 tests)
Verifies the bitrate-clamp logic added to /dub/download-mp3:
- Normal values (128/192/256/320) pass through as Nk
- Case-insensitive (256K → 256k)
- Below-floor snaps to 64k
- Above-ceiling snaps to 320k
- Malformed (None/empty/garbage/scientific) → default 192k
- Negative int parses fine, clamps up to 64k floor
### tests/frontend/apiClient.test.mjs (9 tests)
Exercises api/client.ts under node:test with a synthetic fetch mock:
- apiUrl normalization (empty → API root, slash prepending, absolute URL passthrough)
- ApiError carries status + detail
- apiFetch resolves 2xx, throws ApiError with JSON detail on non-2xx
- apiJson parses body
- apiPost stringifies JSON bodies + sets Content-Type
- apiPost hands FormData straight to fetch (no Content-Type override)
### tests/frontend/format.test.mjs (5 tests)
Covers utils/format.js formatTime timecode rendering.
## Legacy mock refresh (not scope-creeping fixes — minimal updates)
- tests/test_api.py: replace stale `backend.main._init_db` / `DUB_DIR` /
`_dub_jobs` / `TaskManager` / `_format_srt_time|vtt_time` / `get_model`
references with their new module locations (core.tasks, core.config,
services.dub_pipeline, api.routers.dub_export, services.model_manager).
Normalize imports to the unprefixed `from services.*` / `from core.*`
form used inside the backend itself — avoids `backend.*` vs
unprefixed sys.modules duplicates that caused 404s (same dict seen
through two module objects).
- tests/test_engines.py + test_router_smoke.py: loosen strict-equality
backend-set asserts to `.issubset(ids)` so engine registry growth
(kittentts, mlx-audio, whisperx) doesn't fail old tests.
- tests/test_engines.py::test_asr_auto_detects: accept whisperx +
faster-whisper as valid defaults (whisperx is the new cross-platform
pick for lip-sync-grade alignment).
- tests/test_dub_transcribe.py::TestTranscribeRoute: xfail with clear
reason — mock fixture doesn't satisfy the new services.asr_backend
bytes-path contract. Logged for a later test-maintenance pass.
- tests/test_api.py::TestStreamingTTS::test_generate_...: xfail with
clear reason — patch target moved from backend.main.get_model to
services.tts_backend.
## CI gating (.github/workflows/release.yml)
Added a single-runner Linux `test` job that the matrix `build` job now
`needs:`. Runs:
- uv sync + apt install ffmpeg
- uv run pytest tests/
- bun install + bunx tsc --noEmit + bun run test (node:test)
Failing tests now block the 4-platform matrix build before it burns
~40 minutes of runner time.
## Frontend test script
frontend/package.json: add `"test": "node --test ../tests/frontend/*.test.mjs"`.
## Totals on this machine
- Backend: 190 passed, 6 xfailed (stale mocks, documented), 3 skipped
(hardware-specific branches), 0 failed
- Frontend: 36 passed, 0 failed
- Typecheck: clean
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
cfb79cdab0 |
ci: add release workflow
GitHub Actions workflow for automated desktop releases on tag push. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |