Commit Graph
23 Commits
Author SHA1 Message Date
252f0d4fac fix(asr): bound whole-file transcription so a stall isn't reported as "can't reach backend" (#656)
A Windows/CUDA user (Vietnam) hit "Can't reach the local backend" only when
dubbing/transcribing. Their log proves the backend started fine — model loaded,
preload complete, 25 models — and the log ends right after
`whisperx transcribing …tmp.wav`. The backend was alive; the *transcription*
stalled (large-v3 ASR contending with the resident TTS model for VRAM on an
8 GB-class GPU), which the UI surfaces as an unreachable backend.

Root cause (class, not instance): the chunked dub pipeline already bounds each
chunk (OMNIVOICE_TRANSCRIBE_CHUNK_TIMEOUT_S), but the *whole-file* transcribe
paths ran unbounded:
  - dub QC re-transcribe (dub_export)
  - dictation (capture)
  - OpenAI-compat /audio/transcriptions
A slow/stuck transcribe on any of these hung the request AND held a GPU-pool
worker — indistinguishable from a dead backend.

Fix: add run_transcribe_guarded() in services/asr_backend.py — a shared
asyncio.wait_for wrapper (ASRTimeoutError, a TimeoutError subclass) with a
generous env-tunable bound (OMNIVOICE_ASR_TRANSCRIBE_TIMEOUT_S, default 300 s).
On timeout the request returns 504 with actionable guidance (backend is alive;
free VRAM / pick a smaller ASR model / use CPU; restart to clear the stuck
worker) instead of hanging forever. Wired into all three whole-file paths.

Docs: new troubleshooting §14 — "Can't reach the local backend during
transcription/dubbing" — explains it's ASR weight/VRAM pressure, not a network/
mirror problem, and corrects the misconception that a "Network → Restricted/Global
mirror" Settings toggle exists (the Network control is LAN sharing). Serves the
#602/#585/#567 "can't reach backend" cluster.

Test: backend/tests/test_asr_transcribe_timeout.py — slow fn raises ASRTimeoutError
with the actionable message, fast fn passes through, subclass-of-TimeoutError so
the openai_compat broad catch still maps to 504.

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 02:46:14 +05:30
79f3e35682 docs(troubleshooting): add stuck-download / incomplete-cache recovery (#622) (#643)
The 'stuck on the download page, model folder has only refs/ no weights' case
(a connection dropping/blocking mid-pull) is a recurring support report but
wasn't in the install troubleshooting guide. Add section 13 with the recovery
steps + antivirus/VPN/mirror escalation + a huggingface-cli manual fallback.
Docs-only; no version bump.

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 11:50:42 +05:30
0e17caa52a fix(install): actionable torch-wheel-download failure + local-wheel recovery (#569) (#574)
#569: on a restricted network the first-run install fails downloading the
~2.5 GB cu128 PyTorch wheel from download.pytorch.org, and the app won't launch.
Two problems: the error told users to "set UV_DEFAULT_INDEX to a mirror" — which
CANNOT redirect torch, because it comes from a *named, explicit* uv index
(uv 0.11 rejects index-name override values and `--frozen` pins the exact wheel
URLs); and there was no way to supply a manually-downloaded wheel.

- Detect a torch/pytorch-host `uv sync` failure and emit torch-specific guidance
  (Clean & Retry → VPN → drop the wheel locally) instead of the wrong mirror
  advice.
- Add a local wheel-drop dir `<env_root>/wheels` (survives Clean & Retry) wired
  via `UV_FIND_LINKS`. On a frozen-sync torch-download failure WITH wheels
  present, retry NON-frozen with find-links so uv re-resolves from the local
  wheels. Verified empirically: a non-frozen find-links sync installs from a
  local wheel fully offline, while a `--frozen` sync ignores find-links — so the
  retry is the only mechanism that can consume a dropped wheel. Best-effort: if
  it can't satisfy, it fails identically to before and the actionable error
  still fires.
- docs/install/troubleshooting.md: new "#12 CUDA PyTorch wheel download fails"
  entry (docs-sync) — the offline wheel-drop path + why a PyPI mirror can't fix
  this index.

Note: an automatic mirror redirect for the cu128 index is intentionally NOT
shipped — uv provides no working override for a named explicit index, so it
couldn't be verified; the offline wheel path is the reliable escape hatch.

Test: sync_failure_is_torch_download host/keyword detection + negative guard.

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 21:04:31 +05:30
af3da58584 fix(bootstrap): force-reinstall setuptools so pkg_resources repair actually works (#248) (#495)
The auto-repair ran `uv pip install setuptools>=75,<80`, which `uv` treats as
"already satisfied" (no-op, "Checked 1 package in 5ms") whenever setuptools'
*metadata* is present but its `pkg_resources` files are gone — the common cause
being Windows Defender quarantining `pkg_resources/`, or a partial extract on a
restricted network. So the repair never restored the files, the post-check
failed, and users hit the #248 dead-end. The error message *also* told them to
run the same no-op command, so the suggested manual fix didn't work either
(reported on Discord, Win11 + RTX 5070 Ti).

Fix: both repair sites in bootstrap.rs now use `--reinstall` (the flag already
used for the ROCm torch repair), which force re-extracts pkg_resources even when
uv thinks setuptools is satisfied. The fail() message and the failure.py hint now
suggest `uv pip install --reinstall 'setuptools>=75,<80'` + an antivirus-exclusion
note, and docs/install/troubleshooting.md (#pkg_resources-missing) is updated with
the real cause (metadata-present/files-missing) + AV guidance.

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 17:06:05 +05:30
Palash DebnathandClaude Opus 4.8 6306f6edae docs: official Docker Hub image palashdeb/omnivoice-studio (#388)
Link the published Docker Hub repo (https://hub.docker.com/r/palashdeb/
omnivoice-studio) as an official image alongside GHCR in the README install
list and docker.md header. Same images, same tags.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 19:00:45 +05:30
Palash DebnathandClaude Opus 4.8 1561ff4428 ci(docker): also publish to Docker Hub palashdeb/omnivoice-studio (#375)
Push the same images (same tag set: :latest rolling main, :stable/:X.Y.Z
releases, :sha-) to docker.io/palashdeb/omnivoice-studio alongside GHCR.
Gated on DOCKERHUB_USERNAME/DOCKERHUB_TOKEN secrets — without them the
build still publishes to GHCR only. Docs-sync: docker.md mirror note.

Requires repo secrets: DOCKERHUB_USERNAME, DOCKERHUB_TOKEN.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 14:08:46 +05:30
2574fccaf6 docs: community docs refresh — README, CONTRIBUTING, SECURITY, SUPPORT, Docker/macOS install (#341)
* docs: refresh community docs to match the project's current reality

- README: download badges now point to releases/latest (were frozen at
  v0.2.7); Intel-Mac note (pre-built bundle is Apple Silicon; source
  works on Intel; pre-built Intel tracked in #279)
- SECURITY: supported-versions table 0.2.x -> 0.3.x + 0.2.7 legacy row
- docs/install/docker.md: tag mapping matches docker.yml after #338 —
  :latest is the rolling main preview, :stable (new) pins releases
- PR template: removed the abolished two-RC/48h-soak ceremony; documents
  continuous-to-main
- CONTRIBUTING: new sections — what bot review looks like (CodeRabbit +
  Greptile), conventional-commit + issue-link expectations, the quality
  gates (cross-platform parity, 21-locale i18n + CJK allowlist, alembic,
  engine back-compat, local-first, loopback security posture), and a
  contribution-licensing grant that keeps the AGPL + commercial
  dual-license viable
- SUPPORT.md: new — channels, before-you-file checklist, expectations
- docs/install/macos.md: Intel caveat aligned with reality

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: codify the docs-sync hard rule — behavior changes update their docs in the same PR

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(agents): rtk rules for Antigravity — token-compressed tool output

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:58:43 +05:30
101cf2a6e7 ci(release): reinstate macOS Intel (x86_64) build target on macos-15-intel (#342)
Intel MacBook users had no installable artifact: the release matrix only
built aarch64-apple-darwin, and Rosetta 2 cannot run arm64 apps on Intel
(it only translates the other direction) — the rationale in the old
"Intel dropped" comment was backwards. Refs #279.

- Add a native `macos-15-intel` matrix leg (GitHub's designated x86_64
  migration target after macos-13 retired Dec 2025; standard image,
  supported through Aug 2027) building --target x86_64-apple-darwin
  with app,dmg,updater bundles.
- Existing per-TRIPLE steps already carry x86_64-apple-darwin cases
  (uv sidecar tar.gz, evermeet.cx ffmpeg/ffprobe — x86_64 Mach-O,
  natively correct on Intel), so the leg flows through the same
  Bundle/Build/Smoke/Verify steps untouched.
- The PR #290 signing path applies automatically: ad-hoc seal from
  tauri.conf.json signingIdentity "-", opt-in APPLE_* stable signing,
  and scripts/verify-macos-signing.sh both gated on runner.os == macOS.
- tauri-action includeUpdaterJson merges the new darwin-x86_64 platform
  key into latest.json alongside darwin-aarch64, so Intel installs
  auto-update on both Stable and Preview channels.
- docs/install/macos.md: table telling users which DMG (aarch64 vs x64)
  matches their Mac, and the from-source fallback for old releases.

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:58:28 +05:30
Palash DebnathandClaude Opus 4.8 948bc76543 macOS: ad-hoc sign so users open without Terminal + signing/notarization verification (#290)
* chore(release): add macOS signing/Gatekeeper/notarization verification

Codify and enforce the macOS build-signing requirements. The release
pipeline built bundles and had opt-in Apple signing, but never verified
codesign/spctl/notarization — unsigned or broken bundles could ship silently.

- scripts/verify-macos-signing.sh: runs codesign --verify --deep --strict,
  spctl Gatekeeper assessment, per-nested-Mach-O signature check, stapler
  validate, and (opt-in) notarytool history. Report-only by default (unsigned
  dev/preview is expected); --require-signed fails on any unsigned/un-notarized
  component so a broken release stops instead of publishing an unsigned artifact.
- scripts/macos-dev-unquarantine.sh: local-dev-only quarantine stripper, with a
  loud "never a substitute for notarization" warning.
- release.yml: new "Verify macOS signing" step on the macOS leg — report-only on
  unsigned paths, STRICT on the opt-in signed stable path (same condition as
  "Configure Apple signing"), so signing/notarization failures fail the job.
- docs/macos-signing-verification.md: the canonical 10-point requirements +
  how-to-verify checklist, cross-linked to docs/install/macos.md and DESKTOP_RELEASE.md.

Verified locally: report-only PASS (exit 0) and --require-signed FAIL (exit 1)
against the real unsigned debug .app; release.yml parses as valid YAML.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(macos): ad-hoc sign bundle so users open it without Terminal (no Apple ID)

The "app is damaged and can't be opened" error is caused by a broken/incomplete
code-signature seal (codesign --verify failed: "code has no resources but
signature indicates they must be present") on the quarantined download — there
is no GUI bypass for that variant on modern macOS, forcing users to run `xattr`.

Give the bundle a VALID ad-hoc signature at build time (free, no Apple Developer
account) via tauri.conf.json bundle.macOS.signingIdentity = "-". Verified through
a real `tauri build`: the produced .app is now flags=adhoc,runtime and passes
codesign --verify --deep --strict. A valid seal flips the Gatekeeper prompt from
the un-bypassable "damaged" to the GUI-bypassable "unidentified developer", which
users clear with right-click → Open / Settings → "Open Anyway" — no Terminal.

Still not notarized (that needs the paid Apple ID), so there's a one-time
confirmation rather than a clean double-click. The opt-in Developer-ID path is
unchanged: APPLE_SIGNING_IDENTITY (env) overrides the "-" default on the signed
stable release.

- tauri.conf.json: signingIdentity "-" (ad-hoc default).
- verify-macos-signing.sh: detect ad-hoc tier; report the no-Terminal GUI path
  in report-only, still FAIL it under --require-signed (production must notarize).
- docs/install/macos.md: lead the Gatekeeper section with right-click → Open;
  keep xattr as fallback for the harsher "damaged"/corrupted-download case.
- docs/macos-signing-verification.md: signing-tiers table + ad-hoc default note.
- release.yml: comment the ad-hoc default + env override on the signed path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 13:26:04 +05:30
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>
2026-06-11 12:15:28 +05:30
Palash DebnathandClaude Fable 5 ea26893bfc fix(scripts): desktop-prod works from cmd/PowerShell via cross-platform launcher (#282) (#333)
`bun run desktop-prod` (and its :run/:upgrade/:pill/:run:pill variants)
invoked `bash scripts/desktop-prod.sh` directly. On Windows, cmd and
PowerShell have no `bash` on PATH unless Git Bash happens to be there,
so the documented from-source install path died with a cryptic spawn
failure before printing anything — the exact first step in issue #282's
repro.

Add scripts/desktop-prod.mjs, a tiny launcher (runs under bun or node):

- macOS/Linux: execs the bash script unchanged — zero behavior change.
- Windows: locates Git Bash via `where.exe bash`, well-known Git for
  Windows install paths, or derived from git.exe's location; explicitly
  skips C:\Windows\System32\bash.exe (the WSL launcher, which would run
  the script inside Linux and wipe/launch the wrong paths).
- No usable bash: prints an actionable error (install Git for Windows,
  use `bun run desktop`, or use the installer) instead of a spawn error.

All flags are forwarded untouched and the child's exit code is
propagated. scripts/desktop-prod.sh itself is unchanged, and
docs/install/windows.md now lists Git for Windows as a prerequisite
for from-source installs.

Refs #282

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 12:15:10 +05:30
Palash DebnathandClaude Opus 4.8 63a0d00f09 fix(asr): PyTorch-Whisper fallback works without cuDNN 8 or preload (#255) (#268)
Windows + NVIDIA users hit `Could not locate cudnn_ops_infer64_8.dll`:
WhisperX/faster-whisper run on CTranslate2, which needs cuDNN 8, but PyTorch
2.8 ships cuDNN 9 and the side-loaded `cudnn8_compat` libs were missing from
the venv. The PyTorch-Whisper backend should have been the fallback, but it
errored "set OMNIVOICE_PRELOAD_TTS_ASR=1" because it only worked when the TTS
model preloaded an ASR head.

- `PyTorchWhisperBackend._ensure_pipe()` now builds its OWN transformers ASR
  pipeline on demand (PyTorch stack → cuDNN 9, no CTranslate2/cuDNN-8), without
  loading the full TTS model and without the preload env var. A constructor-
  passed pipe (when the TTS model already has one) is still reused. Model is
  overridable via OMNIVOICE_PYTORCH_ASR_MODEL.
- dub_core transcribe preflight no longer hard-rejects pytorch-whisper when no
  pipe is preloaded — it lazy-loads; any failure surfaces per-chunk with the
  real cause.

So a Windows box without cuDNN 8 can switch ASR backend to "PyTorch Whisper"
in Settings → Models and transcription works. Docs: troubleshooting entry.

Tests: tests/test_pytorch_whisper_fallback.py (lazy standalone build, reuse of
a passed pipe, no get_model() call, env override). Full tests/ suite: 700 pass.

Does NOT close #255 — pending the reporter confirming the fallback works on
their machine; the cuDNN-8 install gap (faster-whisper path) is a follow-up.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 08:23:58 +05:30
Palash DebnathandClaude Opus 4.8 b4f1fe18d7 fix(server): relax loopback gate in headless server mode so Docker admin UI works (#261) (#263)
In Docker the loopback origin gate (`require_loopback`) is unenforceable:
Docker's NAT rewrites `request.client.host` to the bridge gateway (e.g.
172.17.0.1) even for a localhost-only `-p 127.0.0.1:3900:3900` mapping, so every
request looks non-loopback. The gate then 403s the operator out of the routes
the web UI needs — `/system/*` (incl. `/system/info`, which left the version
blank, re-breaking #249 in Docker) and `/api/settings/*` (HF-token entry) —
surfacing as "Loopback origin required" all over the UI.

Fix: add an explicit, opt-in `OMNIVOICE_SERVER_MODE` flag. When set,
`require_loopback` becomes a no-op; exposure is then governed by the operator's
port mapping plus the optional share PIN (NetworkAccessMiddleware still 401s
unauthenticated non-loopback clients whenever a PIN is set). The Docker image
sets `OMNIVOICE_SERVER_MODE=1` (Dockerfile + documented in compose).

Security: the desktop build NEVER sets this, so its loopback boundary is
unchanged — LAN share guests are still denied the admin/system routes. New
unit tests lock the contract (strict 403 by default incl. the PR #81 vectors;
relaxed only under the flag). Existing non-loopback 403 tests still pass.

Docs: docker.md troubleshooting entry for "Loopback origin required".

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 06:22:25 +05:30
Palash DebnathandClaude Opus 4.8 621c263354 fix(version): surface running version in web/Docker UI + hide desktop-only updater (#249) (#258)
The Docker web build has no Tauri runtime, so Settings → About → Version read
`getVersion()` (Tauri-only) and rendered a dash — leaving Docker users unable
to tell which version they were running (issue #249). The update-channel toggle
was also shown there even though the auto-updater is desktop-only.

- Backend: expose the single-source `APP_VERSION` over HTTP — add it to
  `/system/info` (`app_version`) and `/health` (`version`). Both are model-free
  and the latter is zero-auth.
- Frontend: Settings → About → Version falls back to `info.app_version` when
  no Tauri `getVersion()` is available, so Docker shows the real 0.3.x version.
- Frontend: hide the update-channel toggle, update-endpoint row, and the
  "Check for updates" button outside Tauri — the Docker image updates by
  pulling a new tag, not via the in-app updater.
- Docs: fix the wrong package name in the version-check command
  (`omnivoice-studio` → `omnivoice`) and document the new `/health` version
  field + the in-UI version row.

Tests: assert `/system/info.app_version` and `/health.version` equal
APP_VERSION (test_router_smoke.py). The stale `:latest` tag itself was already
fixed in #252; cutting a v0.3.x release repopulates it.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 02:15:11 +05:30
Palash DebnathandClaude Opus 4.8 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>
2026-06-02 22:23:56 +05:30
Antony-hash512andgreptile-apps[bot] 506fa8ea49 Add Arch Linux installation instructions (#209)
* Add Arch Linux installation instructions

Added installation instructions for Arch Linux.

* Update docs/install/linux.md

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

---------

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-06-01 08:08:45 +05:30
Palash DebnathandClaude Opus 4.8 1b08c03da9 fix(docker): runtime API-base override so served deployments reach the backend (#174)
Docker users reported Settings -> Engines failing with 'Failed to load engines:
Failed to fetch'. Two problems: (1) the two API-base resolvers diverged
(client.ts honored VITE_API_URL; apiBase.ts honored VITE_OMNIVOICE_API), and
the docs documented VITE_OMNIVOICE_API -- which the Engines request path
ignored; (2) VITE_* is inlined at BUILD time, so a prebuilt ghcr.io image has
no working runtime override at all for reverse-proxy / split-origin deploys.

- backend: when OMNIVOICE_PUBLIC_API_BASE is set, inject it into index.html as
  window.__OMNIVOICE_API_BASE__ (core/spa_inject.py; validated to a plain
  http(s) URL so it can't break out of the <script>). Unset (default) ->
  StaticFiles serves index.html untouched (same-origin, zero overhead).
- frontend: both resolvers (client.ts _resolveApiBase + utils/apiBase.ts) now
  read the runtime global FIRST, then VITE_OMNIVOICE_API/VITE_API_URL, then
  fall through to same-origin. client.ts also strips trailing slashes and
  recognises __TAURI_INTERNALS__ (parity with apiBase.ts/external.ts).
- docs: docker.md + troubleshooting.md document OMNIVOICE_PUBLIC_API_BASE as the
  runtime override that works on the prebuilt image (the old VITE_OMNIVOICE_API
  docker run -e example never worked -- build-time inlining).
- tests: spa_inject helpers (inject + URL validation/breakout); resolver
  precedence for the runtime global + VITE_OMNIVOICE_API in both test files.

Default same-origin behavior is unchanged on every platform; override is opt-in.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 20:54:49 +05:30
Palash DebnathandClaude Opus 4.8 f757b77a59 docs(install): clarify macOS Gatekeeper "damaged" workaround (#134) (#155)
Reword the macOS install doc's Gatekeeper section so it's findable by the
exact symptom ("App is 'damaged' / can't be opened"), and spell out both the
GUI path (right-click -> Open, or System Settings -> Privacy & Security ->
Open Anyway) and the Terminal path (xattr -dr com.apple.quarantine ...).

Explains WHY macOS shows "damaged" (the build isn't notarised yet, so it gets
quarantined) and why the workaround is safe (downloaded from the official
repo/Releases). Proper fix remains Apple Developer signing + notarisation,
already wired in release.yml behind the documented secrets.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 08:12:35 +05:30
Palash DebnathandClaude Opus 4.8 79473c4c01 docs(#124): document AMD GPU (ROCm) install path (#151)
Detection already works (get_best_device + HSA_OVERRIDE_GFX_VERSION); the
gap was that the default install ships CUDA torch, so AMD users fell back
to CPU with no guidance. Document the opt-in ROCm wheel swap (rocm6.2),
the device-verify one-liner, and the HSA override for unsupported GFX.

Linux-only, opt-in — default cross-platform behavior unchanged. An
installer-integrated env-var-driven wheel selection is a tracked follow-up.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 19:20:23 +05:30
Palash DebnathandClaude Opus 4.8 adf486ee03 chore: set version to 0.3.0 across all sources (+ drop v0.4 references) (#145)
* chore: drop stray v0.4 references — everything ships on the v0.3.0 line

Per the project's versioning rule (no v0.4, no unprompted version chatter):

- backend/main.py + marketplace.py: the app reported version "0.4.0" (ahead of
  even pyproject's 0.2.7 and referencing a forbidden version). Aligned to
  "0.2.7" to match pyproject.toml / tauri.conf.json — a consistency fix, not a
  bump.
- errorDocsMap.ts / indextts/bootstrap.py / _secret_key.py: reworded "v0.4"
  deferral comments to version-agnostic "deferred / later hardening pass".
- docs/install/troubleshooting.md: the "tracked for v0.4" notarization line now
  matches macos.md (signing is wired; activates on the Apple cert secrets).

Note: historical planning records under .planning/ still contain "defer to v0.4"
notes; left as-is (a record of superseded decisions) — CLAUDE.md + the
constitution are the live source of truth.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: set version to 0.3.0 across all sources (current dev line)

The current/upcoming version is v0.3.0 (0.2.7 is the prior stable). Bump every
version source so the codebase consistently reports 0.3.0 — the in-code dev
version; the git *tag* still happens later per the release cadence.

- pyproject.toml, frontend/src-tauri/Cargo.toml, tauri.conf.json,
  frontend/package.json: 0.2.7 → 0.3.0
- backend/main.py (FastAPI) + marketplace.py export metadata → 0.3.0
  (these had drifted to a phantom "0.4.0")
- CHANGELOG.md: "[0.2.7] — Unreleased" → "[0.3.0] — Unreleased"
- uv.lock + Cargo.lock reconciled (1-line each) so `--frozen` installs hold.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(version): read app version from package metadata (no more drift)

Greptile (#145): the FastAPI version + marketplace bundle metadata were bare
string literals — they'd go stale-wrong again at the next bump (the exact class
of bug this PR fixes; that's how "0.4.0" happened). Read once from
importlib.metadata.version("omnivoice") via core.version.APP_VERSION, with a
"0.3.0" fallback only for a non-installed source checkout. pyproject.toml is now
the single source of truth for the runtime version.

Tests: tests/test_app_version.py (semver + equals installed metadata). 2 pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 12:08:56 +05:30
Palash DebnathandClaude Opus 4.8 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>
2026-05-29 11:40:27 +05:30
Palash DebnathandClaude Opus 4.8 c34bc002a3 fix(bootstrap): mirror cascade + system-Python fallback for blocked networks (plan-03, closes #60) (#140)
* fix(bootstrap): mirror cascade + system-Python fallback for blocked networks (#130)

plan-03. First-run bootstrap downloaded managed Python from GitHub with no
mirror and a short retry budget, so a GitHub-blocked/unresolvable network
killed the install dead-on-arrival (#60).

bootstrap.rs (Rust/Tauri):
- apply_uv_http_env(): UV_HTTP_TIMEOUT=120 / CONNECT_TIMEOUT=30 / RETRIES=5 on
  both `uv venv` and `uv sync`.
- `uv venv` cascade: default GitHub → gh-proxy mirror (UV_PYTHON_INSTALL_MIRROR)
  → system Python (UV_PYTHON_PREFERENCE=only-system, only if a system Python
  >=3.11 is detected). First success wins.
- Actionable failure messages (install python.org Python / set a mirror / Clean
  & Retry) instead of a raw uv exit code.

Frontend: BootstrapSplash hint for the GitHub-blocked / can't-download-Python
case. Docs: troubleshooting.md restricted-network section (mirror env vars,
China PyPI index, honest VPN note) — referenced by the remediation text.

Tests: Rust #[cfg(test)] for parse_py_version + apply_uv_http_env (cargo test:
2 passed, crate compiles); docs-drift validator + frontend build green.

NOTE: the restricted-network E2E paths (mirror install, only-system fallback)
need MANUAL verification on a real GitHub-blocked network — not reproducible in
the dev/CI harness. cargo + the unit tests cover compile + the pure helpers only.

Closes #60. Addresses #130, #57, #127.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(bootstrap): drop --python 3.11 pin on system-Python fallback (Greptile #140)

system_python_ge_311() accepts 3.12/3.13, but the fallback passed `--python
3.11`, forcing uv to find a 3.11.x interpreter exactly — so a machine with only
3.12/3.13 failed the fallback and wrongly hit the remediation. Drop the pin;
`only-system` + the project's `requires-python = ">=3.11"` lets uv resolve any
compatible system interpreter. cargo test: 2 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 10:44:41 +05:30
Palash DebnathandClaude Opus 4.7 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>
2026-05-20 06:22:10 +05:30