* feat(settings): factory reset gets scopes — preferences, settings, assets, everything
Factory reset did exactly one thing: clear localStorage. The only other option
was "Remove all data", which deletes the Python env and quits. Between "forget
my theme" and "wipe the machine" sat every reset a user actually needs — drop a
corrupt model download, remove a wedged sidecar engine, put the settings back
without losing a single voice — and none of them existed.
Settings → Storage → "Reset & remove" now offers four tiers (UI preferences /
all settings / downloaded assets & models / everything OmniVoice did) plus a
per-scope checklist. Every scope shows its real on-disk size, and the number on
the confirm button is exactly what gets freed.
Why the shell and not the backend: a loaded model memory-maps its weights out of
the HF cache (locked on Windows while mapped), and ensure_dirs() runs at import,
so a backend cannot delete voices/ or outputs/ and still write to them. reset.rs
stops the backend, deletes, and starts it again — and that restart is also the
repair: the fresh process re-runs ensure_dirs() and alembic, so a removed
database comes back empty rather than missing. retry_bootstrap's respawn path is
extracted to bootstrap::respawn_backend so both callers share one implementation.
Deliberate scope choices:
- "Everything" stops short of the managed Python env, so a reset hands back a
working app on the first-run screen. The env is the uninstaller's business.
- A settings reset keeps the storage locations (config.json, the user env file).
Clearing the model-cache pointer would strand gigabytes at a path the app no
longer looks in — install shape is not a preference.
- content deletes the DB with the media: rows without files is how you get a
library full of broken entries.
- The shared HF cache is flagged as shared only when it IS — computed, so Windows
and portable installs (app-private cache) get no caveat they don't need.
Safety: nothing is removed unless it sits inside a validated root — one carrying
an OmniVoice-owned path component OR holding an OmniVoice signature file, which
is what lets a custom data dir on an external volume be cleared while a mis-set
data_dir: "/" is refused. Voices/projects/audio need the word typed.
9 Rust tests (guard, scope composition, shared-cache computation) + 14 frontend
(planning purity, typed confirm, disk-vs-frontend split, shared warning).
Border utilities follow the design guard (tests/test_no_literal_borders.py).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(settings): give the Storage panels a design — proportional sizes, live totals, real tokens
"Remove all data" listed four folders as a flat run of text: a 7.5 GB model cache
and a 391-byte config file rendered at identical visual weight, so the one thing
worth seeing — where the space actually went — was the one thing you couldn't.
And the 391 B folder said "0 KB", which reads as "nothing here".
- New shared StorageTargetRow, used by BOTH destructive panels so they read as
one system: icon, label, dimmed path (truncated, full text on hover), size, and
a proportional bar showing that row's share of what will be freed. Unticked
rows claim none of the bar — the bars must sum to what the button promises.
- The shared HF cache moves OUT of the confirm dialog into its own "Optional"
row with the checkbox and the caveat in the list. Ticking it now moves the
running total in front of the user, instead of springing a different number on
them at the point of no return. The dialog lists exactly what is going.
- One byte formatter for both panels (settings/bytes.js). models/format.fmtBytes
floors at kilobytes, hence "0 KB"; it stays where it is for the model store.
Real fix underneath: three of the tokens these panels styled with DO NOT EXIST
(--chrome-fg-subtle, --chrome-bg-raised, --color-warning). An undefined var()
makes the declaration invalid, the browser drops it, and the element silently
inherits — which is why the paths that were meant to recede rendered at full body
weight. That is a whole class of bug that fails invisibly, so it gets a guard:
src/test/cssTokens.test.js fails on any var(--token) in JSX not defined in a
stylesheet, with runtime-injected tokens (Radix, inline-style hues) allowlisted
by reason. Six pre-existing offenders elsewhere in the app are recorded as
known-broken and ratcheted so the list can only shrink — they are real bugs, but
each is a visual change that wants its own review.
Frontend suite 1196 → 1205 (6 UninstallPanel component tests incl. the live
total and the bar proportions; 3 token-guard tests, verified fail-before).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: green CI + finish the token sweep + snapshot the panels
Three things on top of the redesign:
1. CI was red on tests/probe/test_probe_i18n.py — removing the eight dead
`factory_reset_*` keys from en.json orphaned them in all 20 other locales
(the probe forbids a non-en key absent from en). Removed them everywhere.
This guard scans locales at pytest time; a frontend-only run never sees it.
2. Finished the undefined-token sweep instead of grandfathering it. Six bare
`var(--token)` references resolved to nothing; the only genuinely undefined,
fallback-less one in shipping panels was `--chrome-input-bg` (input fields
AND progress-bar tracks AND skeletons across StoragePanel, StorageUsagePanel,
HistoryRetentionPanel, ModelStoreTab — tracks were rendering with no
background at all). Repointed to --chrome-hover-bg. The rest
(--chrome-menu-bg, --chrome-bg-inset, --border, --input-bg, --muted) already
carry `var(--x, fallback)`, which is valid CSS. So cssTokens.test.js now
checks only the BARE form and ships with zero exceptions — no known-broken
ratchet, because there is nothing left broken.
3. Registered both Storage panels in the visual-regression harness (a Tauri
`invoke` stub added to providers.jsx alongside the existing fetch stub) and
committed baselines across all three themes. This is how I actually looked at
the redesign: the bars render proportional (the 720 KB voices row fills, the
391 B row is a sliver), the shared-cache row sits in its own Optional group,
and every token now resolves in default/midnight/catppuccin. `_forceAdvanced`
on ResetPanel opens the checklist for the snapshot; no effect on the toggle.
Full backend suite 2897 passed (incl. the i18n probe). Frontend 1205.
* style: oxfmt the new panels and specs
Format-check is a CI gate (oxfmt --check); the new files weren't run through
oxfmt --write. No behavior change.
* chore: stop tracking the node_modules symlink
A worktree-local symlink slipped past .gitignore (which lists node_modules/ —
the directory form — so it never matched the symlink file). Removed from the
index; the symlink stays on disk for local test runs.
---------
Co-authored-by: mergetest <nizam4103@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>