* fix(gallery): rename to "OmniVoice Gallery" + fix dark-theme dropdown colors
The gallery heading now reads "OmniVoice Gallery" (gallery.title, all 21
locales — brand prefix on each localized word).
The facet filter <select>s (Gender/Age/Pitch/Accent/Language) rendered with
the OS-default light control surface on the dark theme: .facet-select set
background/border from --bg-tertiary / --border-color, which are defined
nowhere. An undefined var() reads as transparent on the sibling <div> filters
(fine over the dark page) but falls back to the native light background on a
form control. Switch to the defined dark-chrome tokens and add
color-scheme: dark + an explicit dark option list so the popup matches across
WebKit / WebView2 / WebKitGTK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(gallery): archetype previews render a noise buzz instead of voice
The Hype Host, The Podcaster and The Vlogger previews played a loud tonal
buzz, not speech. The preview renderer pinned num_step=16 and seed=42; the
"social" sample script at that exact point lands on a degenerate diffusion
trajectory and collapses to a near-pure tone. The blank-audio guard missed it
because the buzz is loud (peaks near -2 dBFS), not silent — so the garbage was
cached and served. The cache key is (instruct, language) only, so it never
self-corrected.
- Bump preview num_step 16 -> 32: reliably converges to speech across the
gallery's instruct/script space (one-time, cached render cost).
- Add a spectral-flatness floor (_is_unusable_audio) so a degenerate tonal
render is rejected like a blank one, reusing the existing retry-on-new-seed
path. Whisper/breathy voices are broadband (high flatness) so they're safe.
Verified: flatness Hype Host 0.001->0.050, Podcaster 0.0002->0.083,
Vlogger 0.004->0.039; whisper control (Calm Guide) 0.239, not flagged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(gallery): stop preview playback replaying stale cached audio
Preview audio is re-rendered server-side when an archetype is fixed, but the
URL is stable and the response carried no Cache-Control — so the WebView's
HTTP cache replayed the first clip it ever fetched (e.g. the old buzz)
indefinitely, even after the server file was corrected.
- Frontend: fetch previews with { cache: 'no-store' } so playback always
pulls current bytes.
- Backend: send Cache-Control: no-cache on the preview response so any client
revalidates against the ETag instead of serving a stale clip.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(e2e): add Playwright UI smoke + gallery specs and preview-quality unit test
UI testing system to catch regressions like "Use design → Importing a module
script failed" (a dead Vite/module server) and the noisy-preview bug.
- Playwright (frontend/e2e): drives the system chromium (no browser download)
against the Vite dev server. ui-smoke mounts all 12 routable views and fails
on any code-split/import failure, uncaught exception, or ErrorBoundary
fallback. gallery.spec asserts the "OmniVoice Gallery" heading, the dark
facet dropdowns (computed bg = rgba(255,255,255,0.04), not the OS-default
light surface), and that opening an archetype in the Designer mounts the
lazy CloneDesignTab. `bun run e2e`.
- backend/tests/test_archetype_preview_quality.py: unit-tests the
_spectral_flatness / _is_unusable_audio guard with synthetic signals
(tone < threshold < speech < noise; loud tone + silence are unusable) and
pins the render constants. CI-safe — no model/GPU.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>