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 <noreply@anthropic.com>
This commit is contained in:
parent
24ee377230
commit
ce0f4c43bc
17
CHANGELOG.md
17
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
|
||||
|
||||
@ -65,10 +65,12 @@ 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):
|
||||
# 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
|
||||
|
||||
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
|
||||
<!-- ── Core styles (local — no CDN dependency for first paint) ────────── -->
|
||||
<link rel="stylesheet" href="/static/vendor/mdi/materialdesignicons.min.css">
|
||||
<link rel="stylesheet" href="/static/style.css?v=1.12.17">
|
||||
<link rel="stylesheet" href="/static/style.css?v=1.12.19">
|
||||
|
||||
|
||||
<!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── -->
|
||||
@ -336,7 +336,7 @@
|
||||
<script src="/static/vendor/wavesurfer-regions.min.js"></script>
|
||||
|
||||
<!-- loader.js: fetches sections → loads JS modules → removes skeleton -->
|
||||
<script src="/static/loader.js?v=1.12.17"></script>
|
||||
<script src="/static/loader.js?v=1.12.19"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -490,7 +490,7 @@ async function loadSettings() {
|
||||
}
|
||||
await refreshTtsBackendAvailability();
|
||||
enhanceSettingsHelp(document);
|
||||
renderSettingsAbout();
|
||||
if (typeof renderSettingsAbout === 'function') renderSettingsAbout();
|
||||
}
|
||||
|
||||
function markSettingsSeen() {
|
||||
|
||||
@ -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 = '<div style="color:var(--red);padding:8px">Failed to load voices</div>';
|
||||
if (list) list.innerHTML = '<div style="color:var(--red);padding:8px;font-size:13px">Failed to load voices: ' + (e.message||String(e)) + '</div>';
|
||||
$('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() {
|
||||
|
||||
@ -22,23 +22,10 @@
|
||||
<div id="lib-plays-list" class="reh-bookshelf"></div>
|
||||
</div>
|
||||
|
||||
<!-- Characters / Cast -->
|
||||
<!-- Characters / Cast — navigates to s-characters to avoid duplicate IDs -->
|
||||
<div class="lib-panel" data-library-panel="characters">
|
||||
<div class="cl-toolbar card" style="padding:12px; display:flex; gap:10px; align-items:center; flex-wrap:wrap">
|
||||
<label class="cl-tool-field">
|
||||
<span class="cl-tool-label"><span class="mdi mdi-tag-multiple-outline"></span> Production</span>
|
||||
<select id="cl-book-filter"><option value="">All productions</option></select>
|
||||
</label>
|
||||
<label class="cl-tool-field" style="flex:1; min-width:180px">
|
||||
<span class="cl-tool-label"><span class="mdi mdi-magnify"></span> Search</span>
|
||||
<input id="cl-search" type="text" placeholder="Name, alias, archetype, tag…" spellcheck="false">
|
||||
</label>
|
||||
</div>
|
||||
<div id="cl-grid" class="cl-grid">
|
||||
<div class="cl-empty">
|
||||
<span class="mdi mdi-account-box-multiple-outline" style="font-size:48px;opacity:0.4"></span>
|
||||
<p>No characters yet.</p>
|
||||
<p class="cl-empty-hint">Run <b>Character sheets</b> from Read Aloud or the Script Rehearser to populate your cast.</p>
|
||||
</div>
|
||||
<div style="padding:32px;text-align:center;color:var(--subtext);font-size:14px">
|
||||
<span class="mdi mdi-account-box-multiple-outline" style="font-size:40px;display:block;margin-bottom:12px;opacity:0.4"></span>
|
||||
<p>Opening Characters…</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user