Commit Graph
6 Commits
Author SHA1 Message Date
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
debpalashandClaude Fable 5 e3ed952371 security: allowlist the publishable PostHog token for gitleaks
.gitleaks.toml allowlists the exact phc_ literal (write-only client key,
public by PostHog's design; #1193) — regex-based so history scans pass
too — plus gitleaks:allow inline markers. phx_ personal keys stay banned;
the guard test still pins the literal to the two canonical files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 11:55:08 +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
debpalash 09f7c92045 feat(analytics): consent-gated lifecycle events — installs, updates, crashes, errors
New content-free events on the same rails as everything else (dual gate:
build token AND explicit consent; allowlisted metadata only; never raises):

- app_installed — once per install, fired the first time the install is both
  consented and configured (consent lands mid-first-run, so the marker is
  only burned when the event was actually sent).
- app_updated — persisted last-version marker vs current; from_version /
  to_version. The marker advances on every startup, consented or not, so a
  later opt-in never replays pre-consent history.
- app_crashed — ONE authoritative source: the backend run sentinel (#1164).
  The desktop shell's markers describe the same deaths (its watcher restarts
  the backend, whose next startup finds the sentinel), so the frontend never
  emits a crash event — no double-count, pinned by test. Props: exit_kind,
  coarse stage, BUCKETED uptime (never raw seconds), version, os.
- error_occurred — hooked into core.error_journal.record: error_class +
  route-head stage only, deduped by journal fingerprint, hard cap 10/session.

Allowlist extended (from_version/to_version/exit_kind/uptime_bucket/
error_class/stage) in backend and frontend alike, now pinned equal by a
locked mirror test. Also writes DATA_DIR/analytics_info.json (present iff
analytics is enabled; removed on opt-out) so the uninstall scripts can send
a consent-gated app_uninstalled ping without any baked token.
2026-07-16 19:56:42 +05:30
debpalash 7036e101e0 feat(analytics): first-run consent prompt — the opt-in becomes visible, never default-on
The opt-in PostHog analytics existed but was buried in Settings → Privacy,
so release builds produced no OSS-useful data. Consent is now ASKED, once:

- SetupWizard gains a consent step (between models and dictation) with two
  equal-weight Yes/No buttons — shown only in builds that ship a destination
  token and only if the user was never asked. Skipping the wizard or jumping
  past via the rail = not prompted = analytics stays OFF.
- Existing installs (the wizard never reruns) get a one-time dismissible
  banner on app start with the same choice; dismiss counts as No. Any
  explicit choice marks analytics_prompted, so the ask never repeats.
- Backend: set_opted_in() also persists the new analytics_prompted pref;
  GET /api/settings/analytics reports it. A broken prefs file reads as
  'not prompted' (may re-ask) but never as consent (fails closed).
- All strings via i18n across the 21 locales.

The privacy promise is unchanged: nothing is sent without an explicit yes,
silence is not consent, and source builds (no token) never even ask.
2026-07-16 19:49:36 +05:30
Palash Debnathandmergetest ea7a7b39b4 feat(privacy): opt-in analytics — hardened, off by default, enforced by code (#1120)
The rejected PR #1110 had a genuinely careful PII-free event design, but shipped
three things a local-first app can't: exception autocapture ON (raw tracebacks —
home paths, and in this codebase HF tokens out of exception messages — bypassing
core.failure.sanitize() entirely), no user consent or disclosure, and 3,069 lines
of PostHog wizard scaffolding. This is the same capability with those fixed.

core/analytics.py, three rules, each enforced and tested rather than promised:

1. OFF unless the user says yes. TWO gates must both be true: a build-provided
   POSTHOG_PROJECT_TOKEN *and* the user's analytics_enabled pref, default False.
   A default install transmits nothing, so "nothing leaves your machine" stays
   literally true for everyone who doesn't opt in. A broken prefs file fails
   CLOSED. OMNIVOICE_ANALYTICS_DISABLED=1 is a hard kill switch above both.
   Withdrawing consent tears the client down immediately — no restart.

2. NO exception autocapture. Explicitly disabled; a test asserts the constructor
   arg, because the SDK's default is the leak.

3. Metadata ONLY, by allowlist. Every property passes sanitize_properties(),
   which DROPS any key not on _ALLOWED_PROPS and refuses long strings — so no
   future caller can leak a take's text, a path, or a voice name by adding a
   field. text_length is the LENGTH; the text itself has no way through.

The person id is a random per-install UUID — not hardware, hostname, or username.

UI: Settings → Privacy → "Help improve OmniVoice" states in the panel exactly
what is sent, exactly what never is, and that it can be turned off — rather than
burying it in a policy. No destination in the build (any source build) → the
toggle isn't shown, because an inert switch would be a lie.

Docs: README FAQ answers "does OmniVoice collect any data about me?" honestly.

Also fixed a bug I'd introduced in my own wiring: the generation event referenced
variables not in scope, and the call site's bare `except: pass` swallowed the
NameError — so the event would have silently never fired. The call site now logs.

12 tests (default-off / opt-in without token still can't transmit / both gates /
kill switch / consent withdrawal / prefs failure fails closed / allowlist drops
text+paths+names / long strings refused / autocapture OFF / never raises /
random install id). Backend 2936 passed; frontend 1211 passed.

Refs #1110

Co-authored-by: mergetest <nizam4103@gmail.com>
2026-07-12 18:04:07 +05:30