Commit Graph
1 Commits
Author SHA1 Message Date
0e2c00a403 feat(privacy): Settings → Usage — local-only insights instead of cloud analytics (#1114)
* feat(privacy): Settings → Usage — local-only insights, the answer to cloud analytics

A PostHog integration was proposed and rejected (PR #1110, closed): sending
usage events to a third-party endpoint would break the one promise this product
is built on — nothing leaves your machine — and local-first is the reason people
choose it over ElevenLabs. But the question analytics was meant to answer ("how
am I using this?") is a fair one, so answer it locally.

services/local_stats.py aggregates the history the app has ALREADY written to
the user's own SQLite DB: takes, audio produced, compute time, starred, active
days, voices/dubs/projects/exports, and distributions by mode and language.
GET /stats/usage serves it over loopback; Settings → Usage renders it.

The three properties that stop this becoming telemetry by accident:
  - READ-ONLY. No new table, column, or event stream. Delete the feature and not
    one byte of stored data changes.
  - NO CONTENT. Counts and totals only — the `text` column of a take is never
    read and never returned; no paths, no ids, no person. Pinned by a test that
    asserts the payload contains no take text, no /Users/ path, no row id.
  - NO NETWORK. There is no client, no endpoint, no token. It has no way to send
    anything anywhere.
The panel states the guarantee in the UI, because a privacy promise the user
can't see isn't worth much.

Route added to the API-surface snapshot (the inventory guard caught it, as
designed — one line: GET /stats/usage).

4 backend tests (aggregation / never-leaks-content / empty install / missing
table degrades to 0) + 4 frontend tests. Backend suite 2924 passed; lint,
format, typecheck clean.

Closes the analytics question opened by #1110.

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

* fix(settings): use the real --chrome-fg-dim token in UsageTab (css-token guard)

cssTokens.test.js is a frontend guard that every var(--…) a component
references actually exists — an undefined custom property with no fallback is an
invalid declaration, so the style silently does nothing. UsageTab referenced
--chrome-fg-subtle, which doesn't exist; the dim sub-label token is
--chrome-fg-dim (what the other settings panels use).

My miss: I ran the full BACKEND suite but only the two new frontend test files,
so this guard never ran locally. Full frontend suite now green (1211 passed).

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

---------

Co-authored-by: mergetest <nizam4103@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 17:15:10 +05:30