Commit Graph
71 Commits
Author SHA1 Message Date
debpalash 8d84c7f679 fix(ui): Optimise segment table column distribution and fix flex stretch layout bug 2026-04-27 00:37:02 +05:30
debpalash 393dd7e8b5 fix: Fix frontend typecheck errors and raise TTS VRAM offload threshold to prevent CUDA OOM 2026-04-27 00:29:32 +05:30
debpalash f8b4673e1f fix(ui): Fix segment row layout collapse, memory bugs, enterprise page, and UI enhancements 2026-04-27 00:21:47 +05:30
debpalash 93e79db9e6 style: extract 22 inline styles from CheckpointBanner, DirectionDialog, SetupWizard, App, CompareModal, AudioTrimmer
- CheckpointBanner: 7→1 (dynamic accent border-left stays)
- DirectionDialog: 5→0
- SetupWizard: 9→3 (dynamic fix-text color stays)
- App.jsx: 6→3 (dynamic zoom stays)
- CompareModal: 2→1 (dynamic accent color stays)
- AudioTrimmer: 1→0

New Misc.css shared file for remaining small-component classes.
Total inline style count: 65→43 (cumulative 127→43, 66% reduction)
All remaining 43 are genuinely dynamic (CSS vars, computed colors,
animation delays, column widths, progress bars).
2026-04-26 17:17:44 +05:30
debpalash bdafd86b2b style: extract 17 inline styles from WaveformTimeline and ErrorBoundary into CSS
- WaveformTimeline: 10→0 inline styles (layout, loading, overlay, error)
- ErrorBoundary: 7→0 inline styles (wrapper, card, title, trace, retry)
- Shared CSS file for both components (WaveformErrorBoundary.css)

Total inline style count: 82→65 (cumulative 127→65, 49% reduction)
2026-04-26 17:07:48 +05:30
debpalash b36bb8495e chore: update license copyright name and contact email 2026-04-26 17:05:36 +05:30
debpalash fc76e79ff8 feat: setup wizard, donate page, CI fixes, performance optimizations, and style extraction
- Implement donate page and migrate API fetching to react-query hooks
- Add setup wizard for batch job management and voice clip editing
- Refactor setup router into package (wizard, models, download sub-modules)
- Fix 9 CI test failures from setup router refactor
- Fix cross-device link error in prefs.py atomic writes
- Fix event loop mismatch in export test fixtures
- Modernize README with architecture diagram and 13 app screenshots
- Defer per-segment disk writes in dub_generate for ~6s faster dubs
- Extract 45 inline styles from Launchpad, KeyboardCheatsheet, DubSegmentRow
- Add playwright dev dep and screenshot capture script
2026-04-26 16:47:00 +05:30
debpalash 811c842a75 feat: implement Voice Gallery feature with backend routing, API client, and frontend navigation integration 2026-04-25 19:29:29 +05:30
debpalash 901eb040a8 feat: add live bootstrap progress bars and log inspection to splash screen 2026-04-25 17:22:51 +05:30
debpalash 4a8b06c25e feat: implement structured progress tracking for model downloads and add local environment variable loading support. 2026-04-24 18:51:53 +05:30
Palash DebnathandClaude Opus 4.7 787c146f61 feat(bootstrap): splash UI with live progress during first-run setup (#25)
v0.2.2 users upgrading from a PyInstaller build saw "Failed to load
engines: Load failed" because the app window opened before the
first-run `uv sync` (5-10 min) could finish populating the venv. The
webview just hung on a blank state.

Wire the setup through properly:

Rust (src-tauri/src/lib.rs):
- New `BootstrapStage` enum: checking → downloading_uv →
  creating_venv → installing_deps → starting_backend → ready (or
  failed { message }). `#[serde(tag = "stage")]` so it serialises as
  a tagged union the frontend can switch on.
- `BootstrapState` exposed via `bootstrap_status` Tauri command so
  React can poll progress.
- `setup()` no longer blocks on `ensure_venv_ready`. Instead spawns a
  background thread that walks the bootstrap, writes stage updates to
  the mutex, then waits up to 60 s for the backend port to answer and
  flips stage to `ready`.
- `ensure_venv_ready` + `spawn_backend` take the progress mutex
  (Option<&Arc<Mutex<…>>>) and set the right stage at each step.
  They now take AppHandle<R> instead of &App<R> so the background
  thread can hold them.

React (frontend/src/components/BootstrapSplash.{jsx,css}):
- Self-contained splash component + `useBootstrapStage()` hook that
  polls the Rust command every 1 s, short-circuits to 'ready' in the
  Vite dev server / non-Tauri contexts.
- Renders a progress bar + step list keyed to BootstrapStage. On
  Failed, shows the Rust-side message.

App.jsx:
- Calls `useBootstrapStage()`, blocks the main UI render until stage
  === 'ready'.

Also bumps pyproject/package/cargo/tauri.conf versions 0.2.2 → 0.2.3.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 17:02:33 +05:30
Palash Debnath c7816b7222 chore: bump version to 0.2.2 (#24)
Adds Linux AppImage bundle (PR #23).
v0.2.2
2026-04-23 07:55:16 +05:30
Palash DebnathandClaude Opus 4.7 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>
2026-04-23 07:47:43 +05:30
Palash DebnathandClaude Opus 4.7 60e19e4eb3 chore: bump version to 0.2.1 (#22)
First release cut with the uv-venv bootstrap (PR #16) + thin installer
architecture + CI cache layers. macOS Intel dropped from matrix;
ARM only.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.2.1
2026-04-23 07:23:27 +05:30
Palash DebnathandClaude Opus 4.7 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>
2026-04-23 07:06:41 +05:30
Palash DebnathandClaude Opus 4.7 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>
2026-04-23 06:52:30 +05:30
Palash DebnathandClaude Opus 4.7 e57448f704 fix(settings): delete model button — encode HF repo_id per segment (#19)
`encodeURIComponent("voxcpm2/voxcpm2-2B-EC")` turns the slash into
`%2F`, which some ASGI layers reject or fail to roundtrip through the
`:path` converter. Frontend was sending `/models/voxcpm2%2Fvoxcpm2-2B-EC`
and getting a silent no-op (or 404 swallowed by the busy-state wrapper).

Encode each path segment instead so special chars in the repo name
still escape but the slash survives as a literal `/` in the URL.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 06:38:38 +05:30
Palash DebnathandClaude Opus 4.7 715909d552 ci: cache Rust deps for Tauri build (~5 min → ~1-2 min on warm runs) (#17)
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>
2026-04-23 06:04:57 +05:30
Palash DebnathandClaude Opus 4.7 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>
2026-04-23 06:04:44 +05:30
Palash DebnathandClaude Opus 4.7 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>
v0.2.0
2026-04-23 05:54:39 +05:30
Palash DebnathandClaude Opus 4.7 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>
2026-04-23 05:04:31 +05:30
Palash DebnathandClaude Opus 4.7 f2539b6719 fix(release): revert post-collect binary filter, keep strip+optimize only (#14)
The binary-filter hack from PR #13 broke Tauri's resource walker on
Linux:

    resource path `.../dist/omnivoice-backend/_internal/libcufft.so.11`
    doesn't exist

PyInstaller's accounting (hook-generated rerun manifests, resource
glob expansion) still referenced the files after they were filtered
out of `a.binaries`, so Tauri's build.rs saw a path that didn't
exist on disk. Removing the post-hoc filter drops that error.

Keep strip=True + optimize=2 — those alone should still shave hundreds
of MB from native libs + bytecode. If the CPU-only torch wheel (PR #11)
+ these two flags aren't enough to get under 2 GB on Linux/Windows,
the next step is splitting the backend into a separately-downloaded
payload rather than trying to force it into one installer asset.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 03:42:39 +05:30
Palash DebnathandClaude Opus 4.7 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>
2026-04-23 03:01:10 +05:30
Palash DebnathandClaude Opus 4.7 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>
2026-04-23 03:01:06 +05:30
Palash DebnathandClaude Opus 4.7 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>
2026-04-23 02:11:55 +05:30
Palash DebnathandClaude Opus 4.7 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>
2026-04-23 01:20:18 +05:30
Palash DebnathandClaude Opus 4.7 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>
2026-04-23 00:47:45 +05:30
Palash DebnathandClaude Opus 4.7 1256e7696a fix(release): add missing tauri npm script for tauri-action (#8)
tauri-action invokes `npm run tauri build -- --target <triple>`. Without
the script, all matrix builds failed with:

    npm error Missing script: "tauri"

The `@tauri-apps/cli` devDep exposes the `tauri` binary in node_modules/.bin,
so the script just forwards to it.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 00:11:33 +05:30
Palash DebnathandClaude Opus 4.7 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>
2026-04-22 23:49:59 +05:30
Palash Debnath fbbeaf3728 Merge pull request #6 from debpalash/release/v0.2.0
release: v0.2.0 — chrome theme, typography, preflight, export drawer, setup wizard
2026-04-22 23:23:44 +05:30
debpalashandClaude Opus 4.7 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>
2026-04-22 23:15:18 +05:30
debpalashandClaude Opus 4.7 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>
2026-04-22 23:08:18 +05:30
debpalashandClaude Opus 4.7 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>
2026-04-22 18:46:01 +05:30
debpalashandClaude Opus 4.7 93d8cd70d5 feat: pre-flight system check + actionable error surfacing
## /setup/preflight endpoint (backend/api/routers/setup.py)
New one-shot health check the setup wizard calls before model install.
Probes every runtime requirement so GPU driver mismatches, missing
ffprobe, low RAM, stale AMD ROCm setups, and unreachable HF no longer
manifest as silent CPU fallbacks or opaque runtime errors.

Checks returned as {id, label, status, detail, fix?}:
  - Operating system + arch
  - Python runtime
  - System RAM (fail <8 GB, warn <12 GB)
  - Disk free on HF cache partition (fail <10 GB)
  - HuggingFace cache writable
  - FFmpeg (required)
  - FFprobe (warn — some endpoints degrade without it)
  - GPU acceleration — vendor-aware detection:
      * Apple Silicon  → MPS available?
      * NVIDIA         → nvidia-smi parse; fail if driver < R555
                         (cu128 wheels we ship need ≥ 555)
      * AMD            → rocm-smi detect; warn if torch not built w/ ROCm
      * none/unknown   → warn, CPU-only note
  - Network reachability to huggingface.co:443

Aggregate: {ok, has_warnings, checks, device}. Wizard blocks forward-
nav on any fail; passes warnings through with a labelled Continue.

## SetupWizard 4-step flow (frontend/src/pages/SetupWizard.jsx)
Insert "System check" as step 1 between Welcome and Install models.
Renders preflight report with pass/warn/fail icons, inline fix
instructions, and a Re-check button for users who resolve a blocker
without restarting the app. Continue button labels shift based on
status ("All good — continue" / "Continue (with warnings)" /
"Resolve blockers to continue").

## Transcribe-stream error clarity
dub_core.py: move ASR / missing-audio preflight out of HTTP-status
error paths into in-stream `error` events, since EventSource on the
client can't read non-2xx bodies and previously surfaced 503s as
opaque "network error" strings. Users now see the actionable message
(e.g. "ASR isn't loaded yet — check Settings → Models") inline.

App.jsx: on transcribe-stream drop before any final segment, force-
close + reject with a pointed message instead of waiting for
EventSource auto-reconnect to thrash against a broken endpoint.

## API client (frontend/src/api/setup.ts)
Add PreflightReport / PreflightCheck / PreflightDevice types + preflight()
call matching the new endpoint.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 18:19:16 +05:30
debpalashandClaude Opus 4.7 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>
2026-04-22 17:49:51 +05:30
debpalashandClaude Opus 4.7 e1966bdcd9 feat(ui): chrome theme + retina typography + launchpad charisma + export drawer + setup wizard + projects + logs footer
## Typography system
- Add Fontsource variable fonts: Inter, IBM Plex Mono, Source Serif 4
- Unify via --font-sans / --font-mono / --font-serif / --font-display
  tokens in ui/tokens.css
- Enable OpenType features globally: cv11 (single-story a), ss01, ss03,
  zero, tabular-nums slashed-zero; font-optical-sizing: auto;
  font-synthesis: none
- Alias legacy --chrome-font-mono to var(--font-mono); purge Google Fonts
  @import and hardcoded "ui-monospace, Menlo" refs across 43+ rules

## Chrome design tokens
- Migrate every panel/button/input to --chrome-* tokens with
  color-mix(in srgb, … N%, transparent) tone tints
- Flat-chromed .glass-panel, .studio-panel, .main-content, .launchpad,
  sidebar, segments, clone/design textarea, settings subsurfaces,
  export drawer, footer, modals
- Kill radial gradients, paper-grain noise, 30s mesh animation
- Rewrite Badge.css, Button.css, Tabs.css, Table.css to chrome tokens

## Launchpad charisma
- Aurora backdrop: three drifting blurred blobs (pink 22s, green 28s,
  amber 32s) behind everything at z=0
- Hero halo + animated sweep line under H1
- Wave bars with per-bar --bar-delay / --bar-dur for breathing stagger
- Action cards: single --card-hue drives bg/border/glow/spotlight
- Cursor-tracked spotlight via onMouseMove → --mx/--my custom props
  → radial-gradient paint (no JS re-renders)
- Eternal breath ring via .lp-glow-layer::after, staggered across
  three cards via :nth-child animation-delays
- All animations gated by @media (prefers-reduced-motion: reduce)

## Export drawer (new)
- ExportModal.jsx/css: 4-tab bottom drawer (Video / Audio / Subtitles
  / Package) rendered via createPortal
- Presets (YouTube, Archive, Web, Podcast, Study), per-track checklist
  with All/None/Dubs-only, MP3 bitrate 128/192/256/320
- Non-blocking: pointer-events: none on outer, auto on sheet; ESC + click
  outside to close
- DubTab: FooterBtn → React.forwardRef; export trigger opens drawer

## Dubbing ETA
- Show elapsed time during generation; genElapsed state + fmtDur helper
  renders .dub-gen-overlay__stats block

## Setup wizard (new)
- SetupWizard.jsx/css: first-run flow for engine probing, model downloads
- Projects.jsx/css: dedicated projects browser page
- LogsFooter.jsx/css: slide-up logs panel
- api/setup.ts: client for setup router endpoints
- App.jsx, NavRail, Header, store wired to new pages

## Version
- frontend/package.json 0.2.0; refresh bun.lock

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 17:49:44 +05:30
debpalashandClaude Opus 4.7 a44167f22f feat(desktop): tauri v0.2.0 config, capabilities, rust entry updates
Bump Tauri app version to 0.2.0 in Cargo.toml, Cargo.lock, tauri.conf.json.
Update capabilities manifest and src/lib.rs entry to wire the new setup
wizard flow and sidecar management.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 17:49:13 +05:30
debpalashandClaude Opus 4.7 994c6cf065 feat(backend): setup wizard router, translation engines, export options, client-disconnect handling
- Add setup router (backend/api/routers/setup.py) for first-run wizard:
  system checks, engine probes, model downloads with progress
- Add translation engines service with pluggable backends
- Add utils/hf_progress for HuggingFace download progress streaming
- Add PyInstaller runtime hooks (numpy compat, torch compiler disable)
- Global exception handler short-circuits h11 LocalProtocolError and
  Starlette ClientDisconnect with HTTP 499 to silence noisy stack traces
  when users scrub or cancel video mid-stream
- /dub/download-mp3 accepts bitrate query param (clamped 64–320kbps)
- Refactor ASR/TTS backends, dub pipeline, engine management
- Update backend.spec for PyInstaller packaging
- Bump pyproject version to 0.2.0; refresh uv.lock

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 17:49:04 +05:30
debpalashandClaude Opus 4.7 d1fd0e5fcb chore: release docs, pin python version, drop stale tarball
- Add docs/RELEASING.md, DESKTOP_RELEASE.md, desktop-build.md for
  release workflow and packaging steps
- Relocate next.md → docs/specs/studio-v1.md (scratch → formal spec)
- Pin Python version via .python-version
- Ignore research/ clones in .gitignore
- Remove stale omnivoice-studio-20260421-1834.tar.gz snapshot

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 17:48:45 +05:30
debpalash 90f63f26e4 chore: update project assets, documentation, and backend services across voice-pro and voicebox repositories 2026-04-21 18:35:13 +05:30
debpalash 5390a0784e chore: perform comprehensive repository-wide updates across voicebox, voice-pro, and TheWhisper research modules 2026-04-21 18:32:49 +05:30
debpalash 52d68d05dc refactor: update backend architecture, expand frontend state management, and synchronize voice-pro research modules. 2026-04-21 18:32:25 +05:30
debpalash 6f124fb175 feat: implement frontend UI components and expand research documentation for voice processing and translation workflows. 2026-04-21 04:31:08 +05:30
debpalash 2c3e12d8de feat: implement responsive layout adjustments for small screens and add cached status visualization to dubbing workflow 2026-04-20 11:33:30 +05:30
debpalash 6e89db0f77 feat: add YouTube/URL ingestion support using yt-dlp and update UI with granular preparation progress tracking 2026-04-19 17:54:07 +05:30
debpalashandClaude Opus 4.7 ee0f8ce64c feat: error boundaries, sidebar search, keyboard cheatsheet, cross-platform tauri configs
- ErrorBoundary wraps each lazy route (Launchpad/Clone-Design/Dub/Settings).
  Crash in one tab → friendly fallback card; rest of app stays functional.
  Errors surface to Settings > Logs > Frontend via console.error ring buffer.
- Sidebar search input (pill shape at top of scroll area) filters projects,
  profiles, gen history, dub history, exports by name/text/seed/path.
- KeyboardCheatsheet: press '?' to open, grid of shortcut kbd pills
  (Navigation / Segment editor / Audio trimmer / Dub). Mac+Windows key labels.
- Dub thumbnail: Launchpad DubProjects cards now load /dub/thumb/{id} via
  DubThumb component with graceful fallback to film icon.
- Tauri cross-platform:
  * tauri.conf.json keeps Overlay+hiddenTitle (macOS-only, ignored elsewhere)
    so mac traffic lights float inside our Header instead of two-bar stack.
  * tauri.macos.conf.json: transparent + minimumSystemVersion 12.0.
  * tauri.windows.conf.json: NSIS + MSI bundles, webview bootstrapper.
  * tauri.linux.conf.json: AppImage + deb + rpm, deb depends on ffmpeg
    and libwebkit2gtk-4.1-0.
- Dub tab idle: right ghost panel hidden until upload; drop zone spans full
  width. Converted 8 glass-panel wrappers to studio-panel.
- Sidebar dub history: filter out empty/und/Auto language tokens (no more '()').

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 05:50:22 +05:30
debpalashandClaude Opus 4.7 810e09bd0e ui: fix dub history '()' artifact + full-width idle drop zone
- Sidebar dub history subtitle: filter out empty/"und"/"Auto" language tokens
  instead of rendering "English (und)" → "()" style literals.
- DubTab idle state: when no video loaded, hide right ghost panel and
  let left drop-zone panel span both columns. Prevents looking "broken"
  before upload.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 05:08:33 +05:30
debpalashandClaude Opus 4.7 15ef65a686 refactor: extract App.jsx into pages/components/api, add studio design pass + NavRail
Frontend:
- Split App.jsx (2850 -> ~1300 lines) into pages/{Launchpad,CloneDesignTab,DubTab,Settings}.jsx
  and components/{Header,Sidebar,NavRail,CompareModal,DubSegmentTable,DubSegmentRow}.jsx.
- Centralize every fetch through api/{client,dub,generate,profiles,projects,system,exports}.js
  with consistent ApiError + JSON error detail extraction.
- Extract utils: constants (TAGS/CATEGORIES/PRESETS/POPULAR_*), languages (LANG_CODES),
  format (formatTime/probeAudioDuration), consoleBuffer (ring for Settings > Logs > Frontend).
- Lazy-load AudioTrimmer, DubSegmentTable, Launchpad, CloneDesignTab, DubTab, Sidebar, CompareModal, Settings.
  Initial bundle 438KB -> 220KB (-50%).
- Virtualize segment table (react-window) + React.memo row; dynamic row height when
  original text row shown. Fix {proj.is_locked && ...} rendering literal "0" on falsy.
- Segment UX: multi-select + bulk voice/lang/delete, Ctrl+D split-at-cursor,
  Ctrl+M merge-with-next, search/filter/speaker filter, char-budget warn
  when translated text >1.3x source, preserve text_original across translations,
  always stream segments via EventSource /dub/transcribe-stream.
- AudioTrimmer: pro-grade zoom/pan/scrub, rAF-throttled drag, peak precompute
  + async refine (keeps UI responsive on 1700s mp3), keyboard shortcuts,
  click-drag = fresh selection, loop preview, Enter/Esc/Space/Home/End bindings.
  Tests: 22 cases in tests/frontend/audioTrim.test.mjs cover encodeWav header,
  peak min/max invariants, drag modes (start/end/region/pan/new), zoom math,
  slice-to-mono, tick interval picker.
- NavRail: left/right vertical icon rail (VS Code style), side persisted to
  localStorage. Removes tab group from Header.
- View-specific sidebar: hidden on Launchpad/Settings; dub gets 3 tabs; clone/design 2.
  app-container grid updated with sidebar-hidden / rail-right variants.

Design pass (hand-drawn "cute" identity):
- Fraunces italic serif for headlines, Nunito rounded sans for body.
- Wobbly non-uniform border-radius on cards/buttons/inputs.
- Warm peach/rose/lime palette across Launchpad, Settings, Header, panels,
  sidebar items, segment table.
- Header HQ: view breadcrumb (pulsing dot + kicker + accent-colored view label
  + active project), live mini-waveform reacting to model status.
- Settings: tabbed Models / Logs / About / Privacy with accent-colored pills;
  Logs sub-tabbed Backend / Frontend / Tauri.
- Clone/Design: two columns, each split into two studio-panels (prompt vs lang/steps;
  voice source vs overrides+synth). Rectangular corners + launchpad-warm gradient.
- Dub tab: migrate panels to studio-panel look.
- Sidebar item overhaul: kind pill + ago timestamp + hover-reveal pill actions,
  accent left-edge bar on hover, click-whole-card = primary action.

Backend:
- Per-segment translate retry + auto-src fallback; surfaces errors per-segment
  with logged type. Tests: 9 cases in tests/test_dub_translate.py (code coverage,
  source-lang resolution, retry/auto fallback, empty text handling).
- Thumbnail extraction during dub upload (ffmpeg @ ~10% offset, scale 320px wide)
  served via GET /dub/thumb/{job_id}.
- Preserve text_original during transcription so cross-language retranslations
  re-run from pristine source, not compounding on prior translation.
- Settings endpoints: GET /system/info, GET /system/logs?tail=N,
  GET /system/logs/tauri, POST /system/logs/clear. FK-safe profile_id NULL on
  history insert to avoid "FOREIGN KEY constraint failed" on stale/preset ids.
- /dub/transcribe-stream SSE: chunked mlx_whisper / pytorch pipeline per 30s
  window, diarization final pass, honors job.aborted.

Tests: 22 frontend trim + 9 backend translate all pass. Production build 220KB
gzipped (from 127KB main-only pre-split, which hid unshipped modules in main).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 04:52:54 +05:30
debpalash 508d4ba116 feat: add audio trimming for reference clips, implement streaming transcription, and refactor ffmpeg utility handling 2026-04-18 22:37:20 +05:30
debpalashandClaude Opus 4.7 67328d04fe refactor: split backend into api/core/services/schemas, harden security + fd pressure, add searchable language picker, fix segment fragmentation
Backend:
- Split monolithic main.py into backend/{api/routers,core,schemas,services}
- core/db.py: allowlist-gated migrations, db_conn context manager (kills SQL injection on ALTER)
- core/tasks.py: lock-guarded listener add/remove/push, snapshot-before-iterate
- services/ffmpeg_utils.py: run_ffmpeg helper with concurrency semaphore, EAGAIN retry, guaranteed reap
- services/segmentation.py: Bengali/CJK/Arabic punctuation, ultra-short tier, stitch_adjacent_shorts,
  bounded-loop merge; public clean_up_segments API
- services/model_manager.py: robust lock.locked() handling
- api/routers/dub_core.py: job_id traversal guard, thread-safe _active_procs, timeouts on ffmpeg/demucs,
  POST /dub/cleanup-segments endpoint
- api/routers/dub_export.py: guarded SSE listener remove, ffmpeg timeouts via run_ffmpeg
- api/routers/exports.py: destination_path validation, safe source resolver, subprocess list-form
- api/routers/generation.py: contextlib.suppress on tempfile cleanup, db_conn usage, safe output-path helper
- api/routers/system.py: try/finally tmp cleanup, subprocess timeouts
- schemas/requests.py: TranslateSegment.id int->str to match hex segment IDs
- main.py: threading.Lock around crash log writes

Frontend:
- components/SearchableSelect.jsx: popover combobox with search, keyboard nav, popular+recent pins, 200-item cap
- App.jsx: wire SearchableSelect for dub language / ISO code / voice-gen language; Clean Up segments button;
  fix blob URL leak (object-shaped prev in setter, unmount cleanup via ref)
- components/WaveformTimeline.jsx: explicit <video> detach instead of innerHTML='' to release decoder
- index.css: ss-* combobox styles matching Gruvbox theme

Tests:
- tests/test_segmentation.py (26 cases), test_dub_transcribe.py, test_dub_export_unique.py, conftest.py

Chore:
- .gitignore: exclude omnivoice.zip, /research/ reference clones
- Remove tracked stray root test scripts + crash_log.txt

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:20:19 +05:30