From 217d92b91a515a7fae63e30e729618b9da1659b5 Mon Sep 17 00:00:00 2001 From: mARTin-B78 Date: Sat, 27 Jun 2026 10:06:06 +0200 Subject: [PATCH] fix: voice inspector TDZ crash + batch ops respect selection (v1.9.7) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - voice-inspector: move `curGender` const before its first use — it was declared at line 86 but referenced at line 61, causing a ReferenceError (temporal dead zone) that silently aborted selectVoice() on every click - Calc dB, Precompute, Batch Seeds now filter to checked voices when a bulk selection is active, matching Benchmark's existing behaviour Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 8 ++++++++ VERSION | 2 +- static/index.html | 4 ++-- static/js/seed-finder.js | 13 ++++++++----- static/js/voice-inspector.js | 2 +- static/js/voice-library.js | 15 ++++++++++----- 6 files changed, 30 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e23ed63..35761a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,14 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi --- +## [1.9.7] — 2026-06-27 + +### Fixed +- **Voice inspector broken**: `curGender` was referenced before its `const` declaration inside `selectVoice()`, causing a temporal dead zone ReferenceError that silently prevented the inspector from opening. Clicking a voice now correctly opens the detail panel again. +- **Batch operations ignore selection**: Calc dB, Precompute, and Batch Seeds now operate only on the checked (selected) voices when a selection is active, matching the existing behaviour of Benchmark. Previously all three always ran on all active / visible voices regardless of selection. + +--- + ## [1.9.6] — 2026-06-26 ### Fixed diff --git a/VERSION b/VERSION index 7bc1c40..fee0a27 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.9.6 +1.9.7 diff --git a/static/index.html b/static/index.html index ce4b826..1b043a8 100644 --- a/static/index.html +++ b/static/index.html @@ -26,7 +26,7 @@ - + @@ -308,7 +308,7 @@ - + diff --git a/static/js/seed-finder.js b/static/js/seed-finder.js index bb12697..f7355dc 100644 --- a/static/js/seed-finder.js +++ b/static/js/seed-finder.js @@ -431,10 +431,13 @@ function attachSeedFinder(voiceId, body) { // writes to the same cache the per-voice Seed Finder reads, so opening any voice // later shows its samples instantly. function seedFinderBatchAll() { - const ids = (typeof activeVoiceIds === 'function') - ? activeVoiceIds() - : (window._voices || []).filter(v => v.enabled !== false).map(v => v.id); - if (!ids.length) { if (typeof toast === 'function') toast('No active voices', 'error'); return; } + const _sfUseSelected = typeof _bulkSelected !== 'undefined' && _bulkSelected.size > 0; + const ids = _sfUseSelected + ? [..._bulkSelected] + : (typeof activeVoiceIds === 'function') + ? activeVoiceIds() + : (window._voices || []).filter(v => v.enabled !== false).map(v => v.id); + if (!ids.length) { if (typeof toast === 'function') toast('No voices selected', 'error'); return; } // In-app config dialog (no browser prompts): pick the seed range, then Start. document.getElementById('seedbatch-overlay')?.remove(); @@ -442,7 +445,7 @@ function seedFinderBatchAll() { ov.className = 'audiobook-overlay'; ov.id = 'seedbatch-overlay'; ov.innerHTML = `
Batch seed generation
-
Generate & cache Seed Finder samples for all ${ids.length} active voices. Already-cached seeds are skipped, so it's resumable; you can cancel anytime.
+
Generate & cache Seed Finder samples for ${ids.length} ${_sfUseSelected ? 'selected' : 'active'} voice${ids.length !== 1 ? 's' : ''}. Already-cached seeds are skipped, so it's resumable; you can cancel anytime.
diff --git a/static/js/voice-inspector.js b/static/js/voice-inspector.js index 36b59a4..d371344 100644 --- a/static/js/voice-inspector.js +++ b/static/js/voice-inspector.js @@ -45,6 +45,7 @@ function selectVoice(wrap) { const langCode = v.lang || wrap.dataset.lang || ''; const flagCc = v.flag || wrap.dataset.flagCc || langCode; const rating = v.rating || 0; + const curGender = v.gender || 'N'; const nameParts = voiceId.split('_'); // Prefer an explicit display name (e.g. the character name for Rehearser voices); @@ -83,7 +84,6 @@ function selectVoice(wrap) { const _gM = {F:'♀', M:'♂', N:'⚥', '':'?'}; const _gL = {F:'Female', M:'Male', N:'Diverse', '':'—'}; const _gC = {F:'g-f', M:'g-m', N:'g-n', '':'g-n'}; - const curGender = v.gender || 'N'; const genderLabelHtml = `${_gM[curGender]||'?'} ${_gL[curGender]||'—'}`; inspector.innerHTML = ` diff --git a/static/js/voice-library.js b/static/js/voice-library.js index 3bb72d7..5a19434 100644 --- a/static/js/voice-library.js +++ b/static/js/voice-library.js @@ -417,7 +417,9 @@ async function clientVoiceLoudness(v) { } async function clientCalculateVoiceDb() { - const voices = visibleLibraryVoices(); + const voices = (_bulkSelected && _bulkSelected.size > 0) + ? (_voices || []).filter(v => _bulkSelected.has(v.id)) + : visibleLibraryVoices(); const errors = []; let calculated = 0; const stats = {startedAt: Date.now(), ok: 0, slow: 0, errors: 0, middleLabel: 'Skipped'}; @@ -622,7 +624,8 @@ function libraryTargetDb() { $('calculate-db-btn').addEventListener('click', async () => { $('calculate-db-btn').disabled = true; - status('Calculating voice loudness…'); + const _calcTarget = (_bulkSelected && _bulkSelected.size > 0) ? `${_bulkSelected.size} selected` : 'visible'; + status(`Calculating voice loudness (${_calcTarget})…`); try { const d = await clientCalculateVoiceDb(); renderVoiceList(); @@ -907,9 +910,11 @@ $('copy-active-voices-btn').addEventListener('click', async () => { // (re)builds it from the wav when missing — so this is purely a warm-up. $('precompute-embeddings-btn')?.addEventListener('click', async () => { const backend = libraryTtsBackend(); - const ids = activeVoiceIds(); - if (!ids.length) { toast('No active voices to precompute', 'error'); return; } - if (!confirm(`Precompute speaker embeddings for ${ids.length} active voice(s) via “${backend}”?\n\nThis warms each voice so the engine caches its .pt and first playback is instant.`)) return; + const _useSelected = _bulkSelected && _bulkSelected.size > 0; + const ids = _useSelected ? [..._bulkSelected] : activeVoiceIds(); + const _scopeLabel = _useSelected ? `${ids.length} selected` : `${ids.length} active`; + if (!ids.length) { toast('No voices to precompute', 'error'); return; } + if (!confirm(`Precompute speaker embeddings for ${_scopeLabel} voice(s) via “${backend}”?\n\nThis warms each voice so the engine caches its .pt and first playback is instant.`)) return; const btn = $('precompute-embeddings-btn'); if (btn) btn.disabled = true; const ov = document.createElement('div');