Commit Graph
28 Commits
Author SHA1 Message Date
debpalash e77feae817 chore(agents): install project development skills 2026-08-20 07:38:35 +05:30
Palash Debnath 74796bb2ab chore(agents): record issue tracker, triage labels and domain-doc layout (#1400)
Scaffolds the per-repo agent configuration the engineering skills assume: where issues live (GitHub Issues via the gh CLI), the five canonical triage labels, and the single-context domain-doc layout. Adds docs/agents/{issue-tracker,triage-labels,domain}.md and an '## Agent skills' block in both CLAUDE.md and AGENTS.md so the two stay in sync.

Documentation only — no runtime code is touched.

Review fixes: CONTEXT.md is now described as 'read it when it exists' rather than stating its absence as permanent, and both directory-tree fences carry a language identifier (markdownlint MD040).
2026-08-07 05:35:09 +05:30
Palash Debnath 5cab8e0149 feat: rename the product to VoiceStudio (previously OmniVoice-Studio)
Renames what users see. The app, the installers, the window title, the
docs and all 21 locales now say VoiceStudio, with "(previously
OmniVoice-Studio)" noted near the title of each doc surface so people
recognise it.

Deliberately NOT renamed, because renaming any of them silently breaks
an existing install — there is no legacy-path fallback anywhere in this
codebase:

  - bundle identifier com.debpalash.omnivoice-studio (MSI UpgradeCode,
    macOS TCC grants, managed venv, WebView localStorage, the
    single-instance lock)
  - data directories OmniVoice / .omnivoice and omnivoice.db
  - the ~150 OMNIVOICE_* environment variables
  - the X-OmniVoice-* HTTP headers (a wire protocol)
  - the published Docker image paths
  - the OmniVoice ENGINE, which is a model name and not this product

tests/test_identity_paths_survive_the_rename.py pins every one of those
so a future well-meaning sweep cannot orphan a user's library.

Linux .deb users install a new package name and should apt remove
omnivoice-studio; that note is in the changelog.
2026-08-07 01:30:58 +05:30
Palash Debnath a23e69d014 chore: point every repo reference at github.com/debpalash/VoiceStudio (#1394)
The repository was renamed. 724 references across 59 files now point at the new URL — README badges, docs, install guides, the updater's releases API call, CONTRIBUTING, the Colab link and the probe harness. GitHub redirects the old URLs, so nothing was broken in the meantime.

Deliberately NOT renamed, because each breaks something on a user's machine: the Tauri bundle identifier (the path to every existing user's data), /usr/lib/omnivoice-studio and the compose container names, and the published Docker image paths.

The image path needed a code change to STAY still: docker.yml derived it from github.repository, so the next build would have published to ghcr.io/debpalash/voicestudio while Docker Hub, a hardcoded literal, stayed put — everyone pulling the documented GHCR path would have kept receiving the last pre-rename image forever. It is now pinned, with a test that fails if it ever derives from the repo name again.

Also makes the probe's repo-name assertion shape-based: it hardcoded the old name and failed on every PR after the rename while the code it tests worked perfectly.
2026-08-06 18:15:56 +05:30
Palash DebnathandClaude Opus 5 2de27038d9 docs(rules): the parity rule governs behaviour, not performance (#1315)
An automated reviewer raised a Critical asking for torch.compile to be disabled
by default on every platform "so the default is uniform", citing the
cross-platform parity rule. Following it would have slowed down every Linux
CUDA user to match hosts that cannot compile at all.

Read literally, the rule forbids GPU support: CUDA, MPS, DirectML and Triton
availability are all host-dependent by design. It was always about what a user
can SEE AND DO, not about throughput — so it now says that, in CLAUDE.md and
AGENTS.md, and .coderabbit.yaml tells the reviewers directly so the finding
stops regenerating every month.

An optimization skipped where it physically cannot work is not a parity
violation. A feature usable on one OS but not another still is.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 15:36:41 -07:00
debpalashandClaude Opus 5 98c6d8bccf docs(rules): make brevity the default for every agent response
Tightens the Token economy directive: shortest response that fully answers,
outlines/tables over prose, no preamble or recap. Applies to every response,
not just status updates. CLAUDE.md and AGENTS.md kept in sync.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 15:16:07 +05:30
debpalashandClaude Opus 4.8 ab48594582 chore(repo): declutter root — move community docs into .github/
GitHub natively recognizes CONTRIBUTING.md, SECURITY.md, SUPPORT.md, and
CODE_OF_CONDUCT.md in .github/ (Contributing link, Security policy tab, and
the community profile all keep resolving), so relocate the four there and
drop four files from the repo root.

Reference fixes in the same commit (no broken links):
- README.md / README_CN.md → .github/CONTRIBUTING.md
- docs/migration/real-time-voice-cloning.md → ../../.github/SUPPORT.md
- SUPPORT→SECURITY link unchanged (both now in .github/, same dir)
- tests/test_issue_fixes.py Discord-link check repointed to .github/
  CONTRIBUTING.md (a missing path would silently skip, dropping coverage)
- CLAUDE.md docs-sync rule paths updated to match

Kept at root by design: README/LICENSE/CHANGELOG/CLAUDE/AGENTS (required or
convention), SPONSORS.md (wired to absolute GitHub URLs in FUNDING.yml, the
sponsor issue template, and sponsors.js), LICENSE-NOTICE.md (pairs with
LICENSE), README_CN.md (README translation, 29 relative links).

Also ignore the local memxt agent-memory DB (memxt.db*) so it stops sitting
loose in the working tree.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 13:32:13 +05:30
debpalashandClaude Fable 5 23f1767e3c feat(analytics): in-repo publishable token — source builds get the same consent-gated analytics (#1193)
Owner-sanctioned reversal: the publishable write-only PostHog client key is
committed as the in-repo default in backend/core/analytics.py and
frontend/src/utils/analytics.ts (env / baked release token still wins), so
source builds show the same first-run consent ask as installers — skip = off,
nothing is ever sent without an explicit yes. Adds an install_channel property
(installer / docker / source) to lifecycle events, stamped by the desktop shell
via OMNIVOICE_INSTALL_CHANNEL and by the Docker image's existing
OMNIVOICE_SERVER_MODE marker. Guard tests now pin the two-canonical-files
allowlist + same-token invariant, and the uninstall-ping info file works on the
default token.

Fixes #1193

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 11:51:40 +05:30
debpalashandClaude Fable 5 367ed1e8e0 docs(agents): AGENTS.md operating contract — token economy + merge protocol for all agents
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 10:52:30 +05:30
debpalashandClaude Fable 5 83f943bead fix: bot-review harvest (16 findings) + deterministic style/locale CI + reviewer configs
Harvested and verified every CodeRabbit/Greptile finding from PRs #1175,
#1189, #1192, #1195: 16 real ones fixed (fallback ASR preflight bypass,
VRAM release on stream exit, typed 409 parity, uv env independence,
path-privacy in errors, MCP clone_voice hardening, CaptureWidget WS
guard, test hygiene), 4 refuted with evidence, rest documented as
deliberate design or deferred.

Deterministic CI replaces hand-enforcement: tests/test_changelog_style.py
(quiet one-liner format) and tests/test_locale_parity.py (21-locale
key/placeholder lockstep with a ratchet baseline) — the latter surfaced
and fixes 151 already-broken locale strings. CodeRabbit/Greptile carry
the house rules via .coderabbit.yaml + greptile.json; CLAUDE.md gains
the harvest-before-merge and never-accept-as-is rules.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 09:47:41 +05:30
debpalashandClaude Fable 5 63fd497caf feat: TTS-only first run, platform-curated ASR, guided OS permissions, parakeet-mlx
Only the TTS model (~2.4 GB) is required on first run; ASR models are
per-platform curated picks (curated_on in models.yaml) installed on demand.
Every transcription surface returns a typed asr_model_missing error with a
one-click download CTA instead of silently pulling multi-GB Whisper weights.
Settings -> Models is a grouped, platform-aware catalog. New guided
permissions UX (wizard System Check + Settings -> Permissions + mic
pre-flight) with native mic-state checks and OS settings deep-links. New
parakeet-mlx engine brings Parakeet TDT v3 to Apple Silicon (language-gated
capture preference so multilingual dictation never regresses). Docs:
expressive-speech page, Flush/Unload + CPU-fallback triage, clone-length FAQ.
Hardening: preflight fails open for custom model pins, ROCm curation no
longer inherits NVIDIA picks, Windows mic probe reads the NonPackaged
consent key, CaptureWidget setup race fixed, offline-cache CI simulation
fixes so empty-cache runners stay green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 15:20:54 +05:30
debpalash 5fdf71595f docs(analytics): tell the truth about the new consent flow everywhere it's described
Docs-sync for the first-run analytics consent work:

- README + README_CN FAQ ('Does OmniVoice collect any data?'): the app now
  ASKS on first run; still nothing without an explicit yes; lists the new
  event kinds (install/update/crash/error-type/uninstall ping) and that
  source builds never even ask.
- CLAUDE.md: the Local-first constraint bullet and the 'no third-party
  telemetry endpoints' note now reflect the owner-sanctioned reality
  (opt-in PostHog EU product analytics behind the first-run prompt,
  2026-07-16); auto BUG REPORTING stays GitHub-prefill only.
- backend/api/routers/system.py: the /stats/usage comment claimed PostHog
  was rejected outright (PR #1110) — reworded: this endpoint stays local
  regardless of consent; the consent-gated path lives in core/analytics.py.
- CHANGELOG [Unreleased]: user-facing entry for the consent prompt + new
  lifecycle events.
2026-07-16 20:01:09 +05:30
mergetestandClaude Fable 5 f609f57fbc chore(release): codify all deployment channels as release rules; preview always builds from main
A release now has an explicit channel checklist (docs/RELEASING.md §5b):
GH Release + stable updater manifest, preview updater channel, GHCR +
Docker Hub in both CUDA and ROCm flavors, and the Docker Hub overview
sync (whose continue-on-error step must be verified by step log — it
403s silently on tokens without description-edit scope).

Preview/RC policy is now enforced, not just documented: release.yml's
preview-gate fails publish_preview dispatches from any branch but main,
since the preview manifest and rolling Docker tags all track main.

Also fixes docs/RELEASING.md §4-5, which still described the pre-2026-06
versioning scheme (tauri.conf.json + Cargo.toml as sources, 'Tauri
ignores package.json') — the exact opposite of the current single-source
rule — and docs/update-channels.md, which invited previews off feature
branches.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 16:12:31 +05:30
a0ad314736 docs: CLAUDE.md refresh — de-rot versions, compress shipped stack research, replace the dead GSD gate (#1025)
Three classes of staleness that actively misled agent sessions:

- The Project section hardcoded "latest stable v0.3.5 / main at v0.3.6"
  — eight releases behind. Now points at the sources of truth
  (frontend/package.json, the Releases page) and documents the current
  AUTO_VERSION_BUMP-off holding behavior instead of a version literal
  that rots every release.
- ~165 lines of May-2026 stack research for five capabilities that have
  ALL since shipped (HF-token panel, prefilled-URL bug reporting, uv
  mirror fallback, Supertonic-3, in-repo docs). Compressed to the
  durable don'ts it established (no telemetry endpoints, no app-side
  GitHub tokens, no setx, no MkDocs, no hf_transfer) plus a pointer to
  prefer what's already pinned.
- The GSD Workflow Enforcement gate referenced /gsd-quick//gsd-debug/
  /gsd-execute-phase skills that exist nowhere in this environment; the
  owner explicitly chose direct edits over restoring them (2026-07-08).
  It cost a real mid-task detour when a subagent correctly refused to
  work under an unsatisfiable rule. Replaced with the owner decision
  and the working conventions that actually bind (merge gating,
  check-the-PR-queue-first).

244 → 83 lines. GSD section markers preserved so the generating tool
can still find its blocks.

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 04:25:14 +05:30
Palash Debnathandmergetest f7b7e2c13a chore(version): pin main to 0.3.8 (revert the post-release auto-bump) (#858)
* Revert "chore(version): main -> 0.3.9 after v0.3.8 release"

This reverts commit 7489bef085.

* chore(release): gate the post-release version-bump behind AUTO_VERSION_BUMP (owner controls bumps)

Owner decision (2026-07-01): keep main pinned to the released version and bump
only on explicit request. The version-bump job now runs only when the repo
variable AUTO_VERSION_BUMP == 'true' (default off), so releasing no longer
auto-rolls main to +1. Documented the override in CLAUDE.md's versioning rule.

---------

Co-authored-by: mergetest <test@local>
2026-07-01 20:18:15 +05:30
5a28c04c44 chore(version): make frontend/package.json the single source of truth (#503)
Five hand-maintained version literals (pyproject, Cargo.toml, tauri.conf.json,
package.json, version.py) drifting is what shipped a 0.3.6 build calling itself
0.3.5 (package.json lagged; the frozen backend's literal lagged). Collapse to
one canonical source.

- frontend/package.json is canonical: vite already injects __APP_VERSION__ from
  it (first-run setup footer + bug reports).
- tauri.conf.json now reads its bundle version from it ("version":
  "../package.json", a supported Tauri v2 feature) — the MSI/dmg/updater version
  can no longer drift from the UI. Removes the most error-prone literal.
- Cargo.toml + pyproject.toml + version.py's _FALLBACK_VERSION remain as
  toolchain-required CI-guarded mirrors, bumped in lockstep from the canonical.
- release.yml: the preview-stamp and version-bump jobs now read/write
  package.json (the canonical) and no longer touch the derived tauri.conf.json.
- tests/test_app_version.py: new test_tauri_version_derives_from_package_json
  guards the path; the lockstep test now checks the mirrors against the
  canonical package.json.
- CLAUDE.md versioning rule updated to document the single-source model.

6 version tests pass.

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 22:50:39 +05:30
364611f7d7 docs(changelog): backfill high-quality v0.3.6 release notes + make it a hard rule (#499)
v0.3.6 was tagged with the "Auto-generated release for v0.3.6…" fallback body
because CHANGELOG.md had no matching section (release.yml's "Extract CHANGELOG
section for tag" step found nothing). Backfill a real, user-facing
`## [0.3.6] — 2026-06-16` section (Longform suite, engine routing, dubbing +
install reliability, FSL→AGPL relicense) and fold the shipped `.ovsvoice`
persona entry out of [Unreleased]. The live GH release body was updated
in-place to match (notes + the per-platform checksum blocks preserved).

Add a "Release notes / changelog" hard rule to CLAUDE.md: every tagged release
(and preview build) gets a high-quality, house-style CHANGELOG section before
the tag — never the auto-generated fallback — since release.yml ships that
section verbatim as the release body.

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 19:04:22 +05:30
6044765f09 chore(version): bring frontend/package.json into the version lockstep (0.3.6) (#497)
Pre-v0.3.6 release sweep found frontend/package.json stuck at 0.3.5 while the
other three version files were 0.3.6. package.json drives the runtime
`__APP_VERSION__` (vite.config.js), so a v0.3.6 build was calling itself "v0.3.5"
in the first-run footer AND in every auto bug report (undercutting the bug-report
feature). Root cause: the release.yml version-bump job only bumped the trio
(tauri.conf.json / Cargo.toml / pyproject.toml), never package.json, and no test
guarded the lockstep.

- Bump frontend/package.json 0.3.5 → 0.3.6 (matches the trip; `--frozen-lockfile`
  still passes — the version field doesn't affect the bun lock graph).
- Add frontend/package.json to the release.yml version-bump job (set absolutely
  via jq so any prior drift self-heals on the next release).
- Add tests/test_app_version.py::test_all_version_files_in_lockstep — fails CI if
  the four files ever diverge again.
- CLAUDE.md versioning rule updated: it's now FOUR lockstep files, not three
  (docs-sync).

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 17:59:44 +05:30
392b573cdf docs(conventions): add "Fix quality" + "Keep main green" hard rules (#493)
Owner-set 2026-06-16. (1) Fix issues properly/future-maintenance-proof — fix the
whole class, add a regression test, harden against recurrence; extra effort, not
extra verbosity. (2) A merge must never break main's CI — verify the full CI
matrix (every .github/workflows/* AND deploy/Dockerfile) before landing, with
explicit guidance that frontend/ is a bun workspace monorepo whose root bun.lock
must be regenerated on any frontend/package.json change (Docker uses
--frozen-lockfile; plain bun install in ci.yml tolerates drift). Motivated by the
#485 bun.lock incident that reddened main's Docker workflow.

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 15:39:40 +05:30
Palash DebnathandClaude Opus 4.8 599f3bcc5c feat(engines): on-demand unload of subprocess-engine sidecars (Action 13) (#406)
Completes the dynamic engine load/unload slice. The idle reaper (#401) frees
sidecar VRAM after 5 min; this adds a user-initiated "free VRAM now" path so
multi-engine users don't have to wait:

- subprocess_backend: `list_live_sidecars()`, `unload_sidecar(id)`,
  `unload_all_sidecars()` via a shared `_force_reap(predicate)` — busy-guarded
  exactly like the idle reaper (non-blocking lock; a sidecar mid-synth is
  skipped, never interrupted; next request respawns it).
- system.py: `/model/loaded` now surfaces live sidecars as unloadable rows;
  `/model/unload/{sidecar:<id>|sidecars}` frees one or all. The existing
  generic flush panel picks these up with zero frontend change.

Also refresh CLAUDE.md stale version notes: main is 0.3.6 (latest release
v0.3.5 + 1 patch); the v0.3.0-as-unreleased framing in the project/cadence
notes is corrected to the v0.3.x continuous-to-main reality.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 13:05:25 +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
Palash DebnathandClaude Fable 5 a949b2c78a chore(version): main is always latest release + 1 — rule, bump to 0.3.6, Docker retag, auto-bump job (#338)
Versioning hard rule (owner-set 2026-06-11), codified in CLAUDE.md:
- main's three version sources (tauri.conf.json, Cargo.toml,
  pyproject.toml) always carry last release + 1 patch; bumped 0.3.5 ->
  0.3.6 now.
- Preview builds stamp BASE-N which now sorts ABOVE the last stable
  (0.3.6-N > 0.3.5) — the updater ordering becomes natural and the
  Windows MSI ProductVersion wrinkle disappears.
- Docker: :latest = rolling main preview; :stable + :X.Y.Z + :X.Y =
  tagged releases. workflow_dispatch still only emits throwaway :sha-.
- release.yml gains a version-bump job: on every stable v* tag it
  bumps main to the next patch automatically.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 12:16:03 +05:30
8fa54ccae4 l10n(zh-CN): full Chinese localization + Windows/settings fixes (absorbed from #66) (#157)
* @
fix: skip torch.compile on Windows where Triton is unavailable

torch.compile with mode="reduce-overhead" depends on Triton, which has no
official Windows support. On Windows the compile call succeeds but generates
code paths that crash at inference time with an OOM-like error
("Cannot find a working triton installation").

Check for Triton availability before compiling so TTS gracefully falls back
to eager mode on platforms without Triton.

Closes #65
SummerSec
@

* feat: comprehensive Chinese (zh-CN) localization for Settings and navigation

Add full Chinese (zh-CN) translation support across the frontend:
- NavRail, Launchpad, Clone/Design tabs, Settings (all tabs)
- Sidebar navigation labels, hero text, action cards, section headings
- Fix: Settings missing General tab in TABS array
- Fix: i18n locale not persisted after page reload (useEffect deps)
- Fix: NavRail key prop spreading into JSX elements

Co-authored-by: SummerSec

* fix: translate production override parameter labels (Speed, t_shift, etc.)

* fix: translate voice design category labels (Gender, Age, Pitch, etc.)

* feat: translate Transcriptions and Voice Gallery pages

* fix: translate gallery category names (Disney, Anime, etc.)

* feat: translate DubTab, personality presets, and voice design presets

* fix: remove duplicated emoji in personality name translations

* fix: correct preset translation keys to match actual preset IDs

* fix: filter natural language from personality instruct to prevent validation error

* fix: handle edge case where instruct has no valid tags

* chore: remove debug logging from personality instruct filter

* fix: address CodeRabbit review — importlib.util, English comment, grammar, theme, placeholder

* fix: localize selected category label in VoiceGallery header

* feat: translate remaining DubTab UI text (CAST, Generate Dub, Translate All, etc.)

* fix: improve ffmpeg detection on Windows, error messages, and yt-dlp download timeout

* feat: add proxy setting in Settings → General for downloading via proxy

* fix: improve ffmpeg detection on Windows, error messages, and yt-dlp download timeout

* feat: allow HTTP_PROXY/HTTPS_PROXY env vars via /system/set-env

* fix: support SOCKS5 proxy, also set ALL_PROXY env var

* fix: increase yt-dlp extractor retries for subtitle 429 errors

* feat: translate prep overlay stage labels (download, extract, demucs, scene)

* feat: translate BatchQueue, VoiceProfile, ToolsPage, Projects pages

* feat: translate SetupWizard, DonatePage, EnterprisePage + fix NotImplementedError handling

* feat: add ffmpeg status + manual path setting in Settings → General

* fix: validate ffmpeg path exists when user sets it manually

* fix: fall back to thread-based subprocess when asyncio raises NotImplementedError on Windows

* fix: pin setuptools<70 — ctranslate2 requires pkg_resources removed in 70+

* fix: translate transcribing overlay text

* fix: complete DubTab zh-CN localization + argostranslate preflight check

* fix: add cmn-Hans language code mapping for Google Translate

* fix: fall back to thread-based pip install on Windows when asyncio subprocess raises NotImplementedError

* feat: add DeepL/Microsoft/LLM credential fields to Settings

* feat(i18n): localize GlossaryPanel, DubSegmentTable, DubSegmentRow

* fix: Windows-safe log rotation handler avoids PermissionError on rename

* feat: persist proxy/FFmpeg/LLM/translation credentials, separate DeepL/Microsoft keys, add glossary max-height scroll and collapse

- /system/set-env writes env.* to prefs.json via prefs.set_()/delete()
- Backend startup reads env.* from prefs.json into os.environ (.setdefault)
- PERSISTENT_KEYS covers proxy, FFmpeg, LLM, DeepL/Microsoft keys
- DeepL uses DEEPL_API_KEY, Microsoft uses MICROSOFT_API_KEY (fallback TRANSLATE_API_KEY)
- DeepL/Microsoft _build_translator reads DEEPL_BASE_URL/MICROSOFT_BASE_URL
- Google/MyMemory/Microsoft translators bypass Windows registry proxy
- Frontend CREDENTIAL_GROUPS splits into 4 groups with password/text fields
- Glossary panel body max-height: 35vh + overflow-y: auto
- Glossary panel can be collapsed via ChevronDown button
- queryClient.invalidateQueries after save for immediate refresh
- SystemInfoResponse adds proxy_url, ffmpeg_ok, ffmpeg_path

* feat(i18n): localize ExportModal with zh-CN support

- Add useTranslation + replace ~50 hardcoded strings with t() calls
- Add exportModal namespace to en.json and zh-CN.json
- Cover presets, tracks, tabs, video/audio/subs/package tabs, license notice, and output footer

* fix: address PR #66 security review feedback

- Regenerate uv.lock against pypi.org (remove TUNA mirror URLs)
- Route HF_TOKEN through huggingface_hub.login() instead of prefs.json
- Add os.chmod(prefs_path, 0o600) for restricted file permissions
- Add warning logs to _WindowsSafeRotatingFileHandler bare except blocks

* docs: add Chinese translation README_CN.md

* docs: add link to Simplified Chinese translation in README.md

* docs: add English/Simplified Chinese cross-links between READMEs

* fix(l10n): restore clickable Discord/email footer links in EnterprisePage

The i18n extraction replaced main's clickable <button onClick=openExternal>
footer links with bare {t()} labels, dropping both the clickable behavior
and the literal Discord URL — which broke test_discord_link_updated
(EnterprisePage missing discord.gg/bzQavDfVV9). Restore both as clickable
links wrapping the translated label, with the hardcoded URL/mailto (URLs are
not translated). Keeps i18n, restores functional parity with main.

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

* chore(l10n): no-hardcoded-CJK rule + enforce; clean dead LLM block; harden set-env

- Add 'Localization (hard rule)' to CLAUDE.md: no hardcoded non-English UI
  text outside frontend/src/i18n/; functional CJK allowed via allowlist.
- New tests/test_no_hardcoded_cjk.py enforces it (allowlists text-processing
  regexes, model/engine vocab & IDs, error matching, demo/eval data, fixtures).
- Settings.jsx: remove dead saveLlm block (Chinese toasts + unused llm* state,
  flagged by CodeQL js/unused-local-variable); render language-picker native
  names from new LANGUAGES export in i18n/index.ts instead of hardcoding.
- main.py: drop unused 'import shutil' (CodeQL py/unused-import).
- system.py: harden FFMPEG_PATH/FFPROBE_PATH set-env (reject control chars;
  defense-in-depth for the py/path-injection finding). Endpoint stays
  loopback-only — network sharing must never expose /system/set-env.

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

* chore: drop unused ui import (Panel) + fix implicit str-concat in cjk test

Clears the two CodeQL notes introduced/attributed to this PR:
- Settings.jsx: remove unused 'Panel' from the '../ui' import (js/unused-local-variable).
- test_no_hardcoded_cjk.py: collapse multi-line message strings to single lines
  (py/implicit-string-concatenation-in-list).

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

---------

Co-authored-by: SummerSec <summersec@qq.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 09:48:21 +05:30
Palash DebnathandClaude Opus 4.8 8b00dc1f4f feat: onboarding demos, opt-in bug reporting, error-docs deeplinks + issue triage (#133)
* feat: onboarding demos, opt-in bug reporting, error-docs deeplinks + issue triage

Working-tree snapshot bundling several in-flight workstreams (v0.3.0):

- Onboarding/demo system: DemoPresetGrid, DictationDemo, DubbingDemo components
  + tests, render scripts (render_demos_omnivoice.py, build_demos.sh,
  build_dub_demo.sh), personalities preview URLs, alembic 0002 voice-profile
  demo fields.
- Opt-in bug reporting: ReportBugButton (prefilled GitHub-issue URL path).
- Error transparency UX: errorDocsMap deeplinks + BootstrapSplash/error wiring.
- Dub workspace: DubSegmentRow/Table, WaveformTimeline, dubSlice tweaks.
- Issue triage: .planning/issue-clusters/ (plan-01..05 root-cause masters,
  GH #128-#132).
- CLAUDE.md: hard rule — everything ships on v0.3.0, no version bumps.

KNOWN GAP (why this is a draft): the generated demo audio assets are NOT in
this tree, and backend/assets/samples/demo_voice.wav is deleted. onboarding.py
guards the missing file (skips seeding the demo profile with a warning), so no
crash — but first-run Launchpad will be empty and /demo_audio/ preview URLs
404 until assets are regenerated via scripts/build_demos.sh. Do not merge
before regenerating + committing the demo assets.

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

* feat(dub): timing strategies — kill audio compression, add Concise + Stretch Video

Replaces the current audio time-compression default (atempo squeeze to fit
slot) that produced chipmunk/alien output on high-density target languages
like Bengali. Two new user-selectable modes; legacy behaviour kept behind
an explicit "Strict slot" choice.

New `DubRequest.timing_strategy` enum (default "concise"):
  - "concise"        Translator trims text to fit at natural rate; if it
                     still overflows, hard-trim at slot with a fade so we
                     never overlap the next speaker. Surface overflow_s
                     per segment so the user can shorten the text.
  - "stretch_video"  Audio plays at natural 1.0× rate. Backend computes a
                     per-segment new timeline; persists a video_stretch_plan
                     on the job. Mux step (dub_export) builds an ffmpeg
                     trim+setpts+concat filter graph that stretches each
                     segment's video portion to match the natural-rate dub
                     audio. Gaps/pre-roll/tail pass through at 1.0×.
                     Sub burn under stretch_video is skipped in one pass
                     (cues would drift).
  - "strict_slot"    Legacy atempo squeeze. Retained for back-compat.

Director rate-bias side-effect (seg_speed *= bias) now gated on strict_slot
only, so "urgent"/"slow" direction tokens keep their instruct effect in
the new modes without chipmunking.

Per-segment fit_status emitted in the SSE done event:
  {status: "fits" | "overflows" | "video_stretched", overflow_s?, stretch_ratio?}
DubSegmentRow's "Sync: 100%" badge (which was lying — sync_ratio was always
~1.0 because the TTS loop pre-trimmed to slot) is replaced with a truthful
"Fits / Overflows +Ns / Video 1.18×" label.

Frontend:
  - prefsSlice.timingStrategy (persisted, store v3→v4 with safe migrate).
  - DubTab footer Segmented control: "Concise · Stretch Video · Strict slot".
  - useDubWorkflow passes timing_strategy on /dub/generate; consumes fit_status.

Tests: tests/test_dub_timing_strategy.py — 13 cases covering schema
defaults/validation, _build_video_stretch_filter_graph (pre-roll, gap,
tail, empty-plan early return, post-subtitle chain-in), and
_video_stretch_plan_for guards. 30/30 existing dub tests still pass.

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

* fix(waveform): surface missing source as "Source media missing" instead of code-4 black box

When a project's underlying media file is gone (moved or deleted between
save and reload) the <video> element fires MediaError code 4 and the
companion audio fetch returns HTTP 404 — both were silently warned to
the console while the user stared at an unresponsive black panel and an
empty waveform.

- WaveformTimeline now flips loadError when the video element rejects
  code 3 (decode) or 4 (src not supported), and tracks `sourceMissing`
  separately so the error UI can name the actual problem.
- The audio decode fallback chain catches HTTP 404 specifically and
  treats it as source-missing instead of loading silent empty peaks —
  an empty waveform on a deleted source is more confusing than a clear
  "Re-upload the video to continue" message.

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

* fix(tray): "Show OmniVoice" reloads when the webview is blank

When the dev Vite server restarts (or the main window is created before
the backend is ready), the webview load fails and the window is left
with `<body></body>` plus a "Could not connect to the server" console
error. Clicking "Show OmniVoice" from the tray menu just re-showed the
broken window — there was no recovery path short of quit+relaunch.

Now the show handler runs a tiny eval after `show()`/`set_focus()` that
calls `location.reload()` only when `document.body.childElementCount === 0`.
A healthy window doesn't blink (body is non-empty); a blank one
self-recovers as soon as the user clicks Show.

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

* fix(#133): bug-report diagnostics field mapping + drop unused imports

Address PR #133 review:
- ReportBugButton: /system/info exposes `platform` + `device`, not
  `os`/`torch_device`/`gpu` — those reads silently dropped OS/GPU from every
  bug report. Map to the real fields (CodeRabbit). Also remove the dead
  `home` local in stripHome (CodeQL unused-variable).
- DictationDemo: drop unused `Loader` import (CodeQL unused-import).

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 17:24:25 +05:30
Palash DebnathandClaude Opus 4.7 aece6f1a7b fix(widget): hide on app load, bottom-center position, exclude from window-state restore (#104)
Three issues with the dictation pill (Whisper-Flow / Ghost-Pepper style):
1. Pill appeared on app load even though `.visible(false)` and the global
   shortcut hadn't been pressed.
2. When shown, it positioned at top-center instead of bottom-center.
3. The "Ready — hold shortcut to speak" idle label rendered inside the pill
   even when no recording was active.

Root causes & fixes:

**(1) `tauri-plugin-window-state` was restoring widget visibility.**
If the user had the widget visible when they quit the app (mid-dictation,
or by clicking the tray's "Start Dictation" while a window was up), the
plugin saved `visible: true` and restored it on next launch — overriding
the `WebviewWindowBuilder.visible(false)`. Fix: add the widget label to
the plugin's denylist, so its state is never persisted. Belt-and-braces:
explicit `win.hide()` on the widget during studio-mode and pill-mode
startup, so any other plugin or stale state can't sneak the window in.

**(2) Position was hard-coded to top-center.**
Changed `LogicalPosition::new(x, 60.0)` (top) to a computed bottom-center
position: `y = logical_screen_height - 64 - 80` (80 px margin clears
macOS dock + Windows taskbar + most Linux panels). Same math in all
three places it's set (global-shortcut handler, tray dictate, pill-mode
pre-position) — identical behavior on macOS/Windows/Linux per the new
CLAUDE.md "default features work on every platform" rule.

**(3) Idle label rendered visually.**
`CaptureWidget.jsx` now returns `null` when `state === 'idle'`. Listeners
stay mounted (hold-to-talk wiring is preserved), only the visual pill DOM
disappears. The slide-in animation triggers on the natural unmount→mount
when state flips out of idle.

Also: lock in two durable rules surfaced in this session:
- CLAUDE.md: "Default features must work on every platform" — platform-
  divergent defaults are a P0 bug; platform-only features must go behind
  explicit opt-in.
- CLAUDE.md: "No RC, no ceremony" — v0.3.0 ships continuous-to-main; tag
  when actually useful; no v0.4 deferrals while v0.3.0 is open.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 09:50:32 +05:30
Palash DebnathandClaude Opus 4.7 a5e1bb3c51 docs(v0.3.0): research + 18 plans for fat-milestone planning (#87)
* docs(phase-5): research opt-in bug reporting

Phase 5 research: prefilled-URL GitHub Issues pattern, default-deny payload,
redaction layer, two-step consent UX, rate/dedup/recursion safeguards,
aggregation across Python/Rust/React error producers. Builds on Phase 1's
links.py + errorDocsMap deeplink infrastructure; uses already-installed
@tauri-apps/plugin-opener (^2.5.4). No new packages required.

Covers REPORT-01..12 with confidence levels, 8 pitfalls, subprocess-engine
error capture handoff to Phase 2, security domain mapped to ASVS, and
3-wave delivery plan (redactor + payload, consent UI, aggregation +
pre-submit search).

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

* docs(phases): research for Phases 2, 3, 4, 6 (Engine + Supertonic + Spikes + Release)

* docs(stack): bump supertonic pin 1.2.3 → 1.3.1 (Phase 3 research finding)

* docs(phases): plan Phases 2-6 for v0.3.0 fat-milestone release

15 new plan files + 2 ADR decision docs across 5 phases. Combined with Phase 1's 3 plans, the v0.3.0 milestone now has 18 PLAN.md files covering all 7 phases (Phase 0 already complete via PR #71).

PHASE 2 (Engine Isolation — 4 plans):
- 02-01: SubprocessBackend primitive + echo sidecar POC + graceful is_available wrap (ENGINE-01/05)
- 02-02: _safe_torchaudio_save helper + migrate 11 WAV write sites + #48 regression (BUG-01)
- 02-03: IndexTTS sidecar entry + venv-probe bootstrap + IndexTTS2Backend rewire (ENGINE-02/03/04/07, closes #42)
- 02-04: Engine Compatibility Matrix UI + /engines/{id}/health route (ENGINE-06)

PHASE 3 (Supertonic-3 + Mirror — 2 plans):
- 03-01: Supertonic-3 engine on SubprocessBackend + SHA pin + license gate (TTS-01..06)
- 03-02: bootstrap.rs mirror cascade + UV_DEFAULT_INDEX migration + frozen enforcement + docs (INST-07..11)

PHASE 4 (Spike-first Adaptive & Specialty — 2 plans + 2 ADRs):
- 04-01: OmniVoice-GGUF hardware-adaptive engine + quant_map + bundled binaries (SPIKE-01, GGUF-01..06)
- 04-02: OmniVoice-Singing subclass + dub pipeline singing mode + segment detector (SPIKE-02, SING-01..05)
- SPIKE-01-gguf.md + SPIKE-02-singing.md ADRs in .planning/decisions/

PHASE 5 (Opt-in Bug Reporting — 3 plans):
- 05-01: Redactor + BugReporter + URL builder + rate/dedup/recursion safeguards + FastAPI router (REPORT-01/02/03/05/06/07/08/10/11)
- 05-02: BugReportDialog two-step consent + PrivacyPanel + ErrorBoundary integration + Rust panic hook (chained) (REPORT-01-Rust/04/09/12)
- 05-03: Dry-run vs 3 historical issues + cross-platform openUrl smoke + Phase 2 subprocess-errors handoff (REPORT-02 smoke, REPORT-03 expansion, REPORT-09)

PHASE 6 (Release + Retro — 4 plans):
- 06-01: rc1 prep — version bump across 4 sources + CHANGELOG + retro stub + PR-73-strategy doc (REL-01/03/06)
- 06-02: CI guards — workflow-parity actionlint + tag-shaped dry-run (Phase 0 retro options B + C; closes release-engineer gap)
- 06-03: PR #73 reimplementation (NOT rebase) — backend-split installer with mirror-cascade integration + pill-mode regression checkpoint
- 06-04: Execute the release — pre-tag gates + 4-OS clean-VM + 48h soak + tag + retro + 3 v0.4 deferral tracking issues (REL-01/02/03/04/05/06)

Scope decisions locked in plans (council session):
- SoniTranslate refactor DEFERRED to v0.4 (Phase 2 ships SubprocessBackend without migrating Soni)
- macOS notarization DEFERRED to v0.4 (Phase 6 ships xattr -cr automation per CLAUDE.md Key Decision #7)
- supertonic pin 1.2.3 → 1.3.1 (already committed in ba63733)
- SPIKE-01 and SPIKE-02 both GO; 13/13 Phase 4 reqs stay in scope
- PR #73 reimplemented, not rebased (93 commits behind main)

All 18 plans validated via gsd-sdk frontmatter.validate + verify.plan-structure.

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-19 09:22:34 +05:30
Palash Debnath e4dbf4c8c0 P0: release.yml typecheck + bind audit + loopback middleware (#84)
Three P0 fixes bundled — foundation cleanup before v0.3.0 phase work. Closes release.yml drift (PR #51's tabs broke v0.3.0 tag releases), production bind exposure (Critic F1), and 9-endpoint LAN gap on /system/* (Critic F2+F3). 5 new tests; 243 full pass.
2026-05-18 22:00:59 +05:30
Palash DebnathandClaude Opus 4.7 766e2f7284 Phase 0 — Gates: cross-platform CI matrix + regression fixture + release smoke (#71)
* docs: initialize OmniVoice stabilization milestone project

* chore: add project config (yolo + balanced)

* docs: domain research for stabilization milestone

* docs: define v1 requirements for stabilization milestone

* docs: add GGUF + singing engine spike requirements (Phase 4 new)

* docs: roadmap revision + CLAUDE.md (7 phases, 62 reqs, +GGUF/SING spikes)

* docs(phase-0): add Gates phase RESEARCH.md

Phase 0 research synthesizes the cross-platform CI matrix, frozen
omnivoice_data fixture, installer post-build smoke, SHA-256 checksum
publishing, and PR-template extension into copy-paste-ready YAML and
Python snippets composed entirely from existing in-repo patterns.

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

* docs(phase-0): add Gates phase CONTEXT, PATTERNS, and PLAN

Phase 0 — Gates is the hard pre-condition for v0.3.x stabilization.
Lays cross-platform CI matrix (macos-14/windows-2022/ubuntu-22.04),
regression fixture (≤200 KB), installer smoke on tag push, SHA-256
checksums in release body + per-OS SHA256SUMS-*.txt assets, PR
template with RC cadence + fixture line, and the open-PR landing
for #51.

Plan covers GATE-01..06; structured into 7 slices (A–G) with explicit
Slice C → Slice G dependency reordering so the new smoke-matrix lands
on main before PR #51 (CONTEXT.md L86 interleave decision).

Plan-checker iteration 2: APPROVED — all 3 BLOCKERs + 3 MAJORs from
iteration 1 resolved (file truncation/Slice-G missing, GATE-06 sibling
PR verification, Slice C ordering, Truth #5 wording, macOS Tauri
WebView avoidance per Pitfall #5, Windows taskkill per Pitfall #2).

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

* test(00-gates): seed regression fixture (GATE-01)

- scripts/seed-test-fixture.py — deterministic builder for tests/fixtures/omnivoice_data/
  - wipes + rebuilds; fixed created_at=1700000000.0; all-zero PCM for byte-deterministic diffs
  - calls backend.core.db.init_db() directly (alembic versions/ is empty — see CONTEXT.md)
  - checkpoints WAL → DELETE on close so no -shm/-wal sidecars pollute git status
  - exits non-zero if fixture > 200 KB
- tests/fixtures/omnivoice_data/{omnivoice.db, README.md} — 8-table empty DB + 1 voice_profiles row
- tests/fixtures/omnivoice_data/voices/test-voice/{profile.json, sample.wav} — 1-sec 24 kHz mono silence
- .gitignore — explicit allow-list (!tests/fixtures/omnivoice_data/**) so the existing
  omnivoice_data/, *.db, *.wav patterns don't hide the fixture from git

Verifies: du = 144 KB on disk; sqlite_master lists 8 init_db tables + sqlite_sequence;
voice_profiles has exactly 1 row id='test-voice'; 0 rows in generation_history.

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

* test(00-gates): add tests/smoke/test_boot_smoke.py (GATE-01)

- tests/smoke/__init__.py — package marker so pytest treats tests/smoke/ as a module
- tests/smoke/test_boot_smoke.py — 4 in-process FastAPI TestClient smoke tests:
    * test_health_returns_ok — /health returns 200 + {status:ok, device:...}
    * test_profiles_endpoint_lists_fixture_voice — /profiles surfaces the seeded
      test-voice row (validates OMNIVOICE_DATA_DIR wiring → DB_PATH → init_db schema)
    * test_system_info_includes_data_dir — /system/info resolves data_dir
    * test_history_endpoint_empty — /history reaches DB and returns []
  Test isolation env vars (OMNIVOICE_MODEL=test, OMNIVOICE_DISABLE_FILE_LOG=1)
  set at module top BEFORE any backend import — pattern from tests/test_router_smoke.py.
  Fixture is copied to a per-session temp dir so the test never mutates the
  checked-in artifact (SQLite file-change counter + runtime subdirs like dub_jobs/
  would otherwise dirty `git status` after every run).
  Failure mode: if tests/fixtures/omnivoice_data/ is missing, pytest.fail at
  import time with the regenerate command.
- .gitignore — tighten the GATE-01 allow-list to ONLY the seed-produced files
  (README.md, omnivoice.db, voices/test-voice/profile.json, sample.wav).
  Prevents future runtime subdirs the backend may create under the fixture
  from being accidentally committed.

Verifies: `uv run pytest tests/smoke/ -q --tb=short` → 4 passed in 1.31 s
(target was < 30 s). `git status` clean after a test run.

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

* docs(triage): record post-planning GitHub state — PR #62, new issues, OOS deferrals

- GATE-06: mark #53 + #61 merged (2026-05-16); add #62 (Wave 1 quick wins) to gate set
- INST-01: note PR #62 implements setuptools pin (closes #58)
- INST-04: note PR #62 lands README docs for #56 workaround
- INST-12: new requirement for #65 Windows Triton/torch.compile OOM (filed post-planning)
- Out of Scope: defer #67/PR #68 (audio effects), #64 (custom model dir),
  PR #66 zh-CN (i18n milestone), #63 (empty-template bug)

PR #62 is the user's own Wave 1 work landed as a separate PR while
GSD planning ran in parallel. Merging it eliminates duplicate work
in Phase 1.

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

* ci(00-gates): add cross-platform smoke matrix (GATE-02)

- New smoke-matrix job on macos-14, windows-2022, ubuntu-22.04
- needs: test, fail-fast: false, timeout-minutes: 10
- Pinned actions: checkout@v4, setup-python@v5, setup-uv@v3 (cache enabled)
- Per-OS ffmpeg + libsndfile install (brew/choco/apt via awalsh128 cache)
- UV_HTTP_TIMEOUT=120, UV_HTTP_RETRIES=5 for restricted-network resilience
- Narrow scope: uv run pytest tests/smoke/ -q --tb=short
- Existing `test` and `tauri-cross-platform` jobs untouched

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

* ci: add workflow_dispatch to ci.yml so smoke-matrix can run on feature branches

* feat(00-gates): add --health-check CLI flag to backend entrypoint (GATE-03)

- argparse on __main__ block; --health-check boots uvicorn in a daemon
  thread and polls http://127.0.0.1:3900/health every 5s for up to 60s.
- Prints 'OK — /health responded 200 after Ns' and exits 0 on first 200.
- Prints 'FAIL — /health did not respond 200 within 60s' to stderr and
  exits 1 on timeout. Default invocation behavior unchanged.
- No new deps (stdlib argparse/threading/time/urllib.request/sys + uvicorn).
- Consumed by per-OS installer-smoke step in .github/workflows/release.yml.

Verified locally: exits 0 in 5s against tests/fixtures/omnivoice_data/.

* ci(00-gates): add per-OS installer smoke to release.yml (GATE-03)

Adds three matrix-leg-specific steps after 'Build + release (Tauri)',
each gated by runner.os with timeout-minutes: 5:

- macOS (macos-14): hdiutil attach DMG → locate bundled Python backend
  inside *.app/Contents (NOT the Tauri WebView shell — RESEARCH Pitfall
  #5: WebView hangs on headless runners) → invoke --health-check →
  hdiutil detach. Falls back to *.app/Contents/Resources and hard-fails
  with a directory listing if no backend binary found.

- Windows (windows-2022): msiexec /quiet install → find backend.exe
  under 'C:/Program Files/OmniVoice Studio' → invoke --health-check in
  background, wait, then taskkill //F //T //PID to cleanup orphaned
  PyInstaller child processes on port 3900 (RESEARCH Pitfall #2).

- Linux (ubuntu-22.04): --appimage-extract (no FUSE on GH runners),
  locate binary or AppRun, run under xvfb-run -a.

Bundle-only regressions (PyInstaller missing-module, Tauri sidecar
path mismatch) are invisible to ci.yml's in-process smoke matrix —
this step closes that gap before any release is published.

Verified: YAML parses; all three steps present; gating + timeout
correct; Pitfall #2/#5 mitigations preserved.

* ci(00-gates): publish SHA-256 checksums in release body + as asset (GATE-05)

- Add 'Compute SHA-256 checksums' step writing SHA256SUMS-<label>.txt
  per matrix leg using native shasum/sha256sum (Git Bash on Windows).
- Add 'Append checksums to release + attach SHA256SUMS file' step using
  softprops/action-gh-release@v2 with append_body: true so the hashes
  land in the release body alongside tauri-action's content (not
  replacing it) and the file is uploaded as a release asset for
  'shasum -c SHA256SUMS-<label>.txt' verification.
- Both steps gated by 'github.event_name == push && refs/tags/v*' so
  workflow_dispatch dry-runs do not attempt to attach to a non-existent
  release (per CONTEXT.md L70 + RESEARCH Pitfall #7 deferral of any
  aggregate cross-leg SHA256SUMS job).
- fail_on_unmatched_files: true to surface path-resolution errors loudly.

* docs(00-gates): document RC cadence + regression-fixture check in PR template (GATE-04)

* docs(setup): add HF token persistence guide for macOS/Windows/Linux (DOCS-05)

Covers two persistent paths:
- Method A — canonical ~/.cache/huggingface/token via huggingface-cli login
- Method B — shell env var (~/.zshrc / ~/.bashrc / Windows User scope)

Documents the v0.2.7 "session only" in-app behavior + notes that
Phase 1 AUTH-03 will make in-app pastes write to the canonical file.

Bundled with Phase 0 PR per user request. Strictly DOCS-05 scope —
zero code changes, no engine touches.

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

* spec(auth): redesign HF token resolution as 3-source cascade with fallback (AUTH-01..06)

Replaces the env_store.py file-based design with a SQLite-backed app
store + cascade resolver that checks app → env var → ~/.cache/huggingface/token
in priority order, with automatic fallback to next source on HTTP 401.

User-explicit design decision:
- App-stored token (SQLite settings table, AES-GCM encrypted) wins
- Env var ($HF_TOKEN) second
- Global huggingface-cli login file third
- All three sources visible in Settings → API Keys with "Active" badge
- Save action populates BOTH app store AND canonical HF file (defense in depth)

New requirement:
- AUTH-06 — on 401, auto-retry next source in cascade before erroring

Also: traceability count corrected (62 → 74 — undercount at planning +
INST-12 + AUTH-06 added post-planning). All 74 v1 reqs mapped.

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

* fix(auth): backend recognizes HF token from canonical file, not just env var

Two call sites were only checking $HF_TOKEN env var, missing the canonical
~/.cache/huggingface/token file written by `huggingface-cli login` (or the
app's future Save action):

- system.py `/system/info` `has_hf_token` flag — UI showed "No HF token"
  even when `huggingface-cli login` had populated the file.
- model_manager.get_diarization_pipeline — pyannote diarization silently
  returned None when only the canonical file was set. This is the bug
  behind issue #35 (speaker diarization setup failure).

Both fixes use the same pattern: env var > huggingface_hub.get_token()
(which reads the canonical file). Adds a local _has_hf_token() helper
to system.py with a comment marking it as prelude to the AUTH-01..06
cascade (Phase 1 token_resolver.py will layer SQLite app-store on top).

Closes #35 sub-issue (canonical token invisible to diarization).
Cross-cuts AUTH-02 + AUTH-06 design for Phase 1.

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

* feat(dictation): make pill-widget mode reachable from GUI + scripts (INST-13)

The dictation widget infrastructure shipped in PR #40 but was only reachable
via the undocumented --pill CLI flag. Adds three discovery paths:

1. Tray menu: "Switch to Dictation Widget" (studio mode) — saves
   launch_as_widget=true to config, relaunches with --pill, exits current.
   Mirrors the existing "Open Studio" path in pill-mode tray.

2. Persistent config: AppConfig.launch_as_widget (bool, default false). Read
   at startup via load_config_pre_app() (uses dirs-next, no AppHandle
   required). CLI --pill still takes precedence when explicitly passed.

3. Tauri commands: get_launch_as_widget / set_launch_as_widget for the
   Phase 2 Settings UI to bind a checkbox to.

4. Scripts: bun desktop-prod:pill / desktop-prod:run:pill — forward --pill
   to the bundled app launch. macOS uses `open -n --args` to spawn fresh
   instance with the flag.

Closes the GUI half of INST-13. Phase 2 closes the Settings UI half.

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

* fix(dictation): show widget unconditionally on pill-mode launch + visible Suspense fallback

Before: pill mode set up correctly but the widget window stayed hidden
until ⌘⇧Space was pressed. New users saw absolutely nothing on launch
(no main window, no dock icon, hidden widget) and assumed the app
failed. If global-shortcut Accessibility permission wasn't granted,
they had no path to discover the widget at all.

Two changes:

1. lib.rs: in pill_mode_setup, explicitly show + position + focus the
   widget window after hiding main. With per-call error logging so we
   can diagnose failures (and a clear error log if widget window
   wasn't created at all — points at tauri.conf.json regression).

2. main-app.jsx: Suspense fallback was `null`, which combined with
   widget's transparent+decorations:false config made any lazy-import
   delay or failure invisible. Now renders a dark pill saying
   "Loading dictation…" so even if CaptureWidget lazy-import stalls,
   the user sees the window exists.

Studio mode behavior unchanged — widget stays hidden until hotkey
or tray click triggers it (existing show() call in the shortcut/
menu handlers is preserved).

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

* fix(dictation): create widget window programmatically; Tauri 2 silently dropped config-array creation

Root cause: declaring the widget window in tauri.conf.json's app.windows[]
silently failed in Tauri 2 — get_webview_window("widget") returned None
even though the config was syntactically valid. Probable culprit was the
transparent + decorations:false + visible:false combo, but Tauri offered
no error message either at startup or via webview_windows() enumeration.

Diagnosed by adding webview_windows() enumeration logging at setup start
(only ["main"] ever appeared) and a programmatic WebviewWindowBuilder
fallback that surfaces real Result errors.

Fix:
- tauri.conf.json: widget entry now has `create: false` to make the
  config-vs-programmatic handoff explicit.
- lib.rs setup(): call WebviewWindowBuilder::new(app, "widget", ...).build()
  with the exact same surface attributes the config used to declare.
- capabilities/default.json: include "widget" in windows array so the new
  window inherits the same Tauri permissions as main.
- tauri.conf.json: remove the invalid `"url": "/?window=widget"` field —
  WebviewUrl::App takes a path only, query strings aren't supported.
  Both windows now load index.html.
- main-app.jsx: replace URL-query-based widget detection with
  getCurrentWindow().label === 'widget' via @tauri-apps/api/window. This
  is the Tauri 2-recommended pattern for multi-window apps and works
  regardless of URL routing.

Closes the immediate UX bug behind the dictation widget being invisible.
Builds cleanly + manually verified: pill widget visible on screen at
top-center after `bun desktop-prod:pill`.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 12:34:32 +05:30