From ce0f4c43bc3225120d52379fb3c70f69b8aed1e3 Mon Sep 17 00:00:00 2001 From: mARTin-B78 Date: Sun, 28 Jun 2026 01:57:49 +0200 Subject: [PATCH] fix: restore My Voices after infinite recursion + settings crash (v1.12.18-19) Root cause 1 (v1.12.18): `window.loadVoiceLibrary = () => loadVoiceLibrary()` overwrites the global binding the arrow function references, causing immediate RangeError: Maximum call stack size exceeded on every call. Changed to direct assignment `window.loadVoiceLibrary = loadVoiceLibrary`. Root cause 2 (v1.12.18): `loadSettings()` called `renderSettingsAbout()` which lives in conversation.js (batch E), loaded after init.js. Guard added with typeof check; nav.js already calls it safely when the About section opens. Also (v1.12.18): s-library.html duplicated cl-book-filter / cl-search / cl-grid from s-characters.html, breaking getElementById. Characters panel in Library now redirects to s-characters instead of duplicating its DOM nodes. Also (v1.12.19): engines.js triggers a second loadVoiceLibrary() after nav.js already rendered voices, blanking the list briefly. Second call now silently re-fetches without clearing the list when voices are already present. Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 17 +++++++++++++++++ VERSION | 2 +- core/voice.py | 10 ++++++---- static/index.html | 4 ++-- static/js/settings.js | 2 +- static/js/voice-library.js | 13 ++++++------- static/sections/s-library.html | 21 ++++----------------- 7 files changed, 37 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9713c4d..8d3d97f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,23 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi --- +## [1.12.19] — 2026-06-28 + +### Fixed +- **Voice list flicker on load** — engines.js (batch E) calls `loadVoiceLibrary()` after nav.js already rendered voices, causing the list to blank out and reload. Second call now runs silently (no skeleton, no status reset) when voices are already present. + +--- + +## [1.12.18] — 2026-06-28 + +### Fixed +- **Diagnostic code removed** — temporary debug IIFE and renderVoiceList try-catch scaffolding cleaned out of voice-library.js; My Voices loads correctly. +- **Settings load crash: renderSettingsAbout is not defined** — `loadSettings()` called `renderSettingsAbout()` unconditionally but that function lives in `conversation.js` (batch E, loaded after init). Guard added with `typeof` check; the About section still renders when opened (nav.js already guards the same call). +- **Duplicate element IDs** — `s-library.html` Characters panel duplicated `cl-book-filter`, `cl-search`, `cl-grid` from `s-characters.html`, causing `getElementById` to return the wrong element. Characters tab in Library now shows a redirect placeholder instead. +- **Infinite recursion in loadVoiceLibrary** — `window.loadVoiceLibrary = () => loadVoiceLibrary()` overwrites the global binding that the arrow function references, causing immediate stack overflow. Changed to direct assignment `window.loadVoiceLibrary = loadVoiceLibrary`. + +--- + ## [1.12.17] — 2026-06-28 ### Fixed diff --git a/VERSION b/VERSION index 6dbba9c..ea90c61 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.12.17 +1.12.19 diff --git a/core/voice.py b/core/voice.py index 2794690..1e9dcb5 100644 --- a/core/voice.py +++ b/core/voice.py @@ -65,11 +65,13 @@ def _is_sound_asset_file(path: Path) -> bool: ) +_AUDIO_EXTS_SET = frozenset(_AUDIO_EXTS) + def _voice_audio_files(root: Path): - for ext in _AUDIO_EXTS: - for p in root.rglob(f"*{ext}"): - if not _is_internal_voice_file(p) and not _is_sound_asset_file(p): - yield p + # Single rglob pass instead of one per extension (6× faster directory scan) + for p in root.rglob("*"): + if p.suffix.lower() in _AUDIO_EXTS_SET and not _is_internal_voice_file(p) and not _is_sound_asset_file(p): + yield p def _find_voice_audio(voice_id: str, scan_dir: Path) -> Path | None: diff --git a/static/index.html b/static/index.html index f39b4c6..f612459 100644 --- a/static/index.html +++ b/static/index.html @@ -26,7 +26,7 @@ - + @@ -336,7 +336,7 @@ - + diff --git a/static/js/settings.js b/static/js/settings.js index edee38e..aa867dd 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -490,7 +490,7 @@ async function loadSettings() { } await refreshTtsBackendAvailability(); enhanceSettingsHelp(document); - renderSettingsAbout(); + if (typeof renderSettingsAbout === 'function') renderSettingsAbout(); } function markSettingsSeen() { diff --git a/static/js/voice-library.js b/static/js/voice-library.js index 5411dba..4c43c15 100644 --- a/static/js/voice-library.js +++ b/static/js/voice-library.js @@ -613,10 +613,9 @@ async function loadVoiceLibrary() { _libraryLoadPromise = (async () => { setBusyButton('refresh-voices-btn', true); const list = $('voice-list'); - if (list) list.innerHTML = loadingMarkup('Loading voice library', 'Scanning voices, reference text, metadata, ratings, and benchmark results.', 8); - $('voice-count').textContent = 'Loading voices…'; - updateLibraryInsights('loading'); - status('Loading voice library…'); + const silent = _voices.length > 0; // already rendered — refresh without blanking the list + if (list && !silent) list.innerHTML = loadingMarkup('Loading voice library', 'Scanning voices, reference text, metadata, ratings, and benchmark results.', 8); + if (!silent) { $('voice-count').textContent = 'Loading voices…'; updateLibraryInsights('loading'); status('Loading voice library…'); } try { const r = await fetch('/api/voices'); if (!r.ok) { const e = await r.json().catch(() => ({})); throw new Error(e.detail || r.statusText); } @@ -628,10 +627,10 @@ async function loadVoiceLibrary() { if (typeof renderPerfHistory === 'function') renderPerfHistory(); status(`Loaded ${_voices.length} voices`); } catch(e) { - if (list) list.innerHTML = '
Failed to load voices
'; + if (list) list.innerHTML = '
Failed to load voices: ' + (e.message||String(e)) + '
'; $('voice-count').textContent = 'Load failed'; updateLibraryInsights('error'); - status('Voice library load failed'); + status('Voice library load failed: ' + e.message); throw e; } finally { setBusyButton('refresh-voices-btn', false); @@ -926,7 +925,7 @@ function mergeBenchmarkResults(d) { if (hit && hit.benchmark) v.benchmark = hit.benchmark; }); } -window.loadVoiceLibrary = () => loadVoiceLibrary(); +window.loadVoiceLibrary = loadVoiceLibrary; window.mergeBenchmarkResults = mergeBenchmarkResults; function activeBenchmarkVoices() { diff --git a/static/sections/s-library.html b/static/sections/s-library.html index 98809ba..1ca01d4 100644 --- a/static/sections/s-library.html +++ b/static/sections/s-library.html @@ -22,23 +22,10 @@
- +
-
- - -
-
-
- -

No characters yet.

-

Run Character sheets from Read Aloud or the Script Rehearser to populate your cast.

-
+
+ +

Opening Characters…