diff --git a/CHANGELOG.md b/CHANGELOG.md index e7f06e6..46111bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,13 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi --- +## [1.12.99] — 2026-07-05 + +### Fixed +- **"Design all" (Script Rehearser bulk voice design) silently did nothing** — confirmed via server logs: `/api/analyze-characters` succeeded every time, but `/api/voice-design` was never once called. Root cause: `rehState.cast[sp].voice` was accessed unguarded right after the (multi-second, for a large cast) analyze-characters request resolved; any cast-entry change during that window threw a TypeError with nothing to catch it, silently killing the whole run with no toast and no further requests. Guarded the access and wrapped the rest of the flow in a try/catch/finally so any future failure surfaces as a toast and always re-enables the button instead of leaving it stuck with no feedback. + +--- + ## [1.12.98] — 2026-07-05 ### Added diff --git a/VERSION b/VERSION index f12b77f..d8e4738 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.12.98 +1.12.99 diff --git a/static/index.html b/static/index.html index 194a12f..3e21e7a 100644 --- a/static/index.html +++ b/static/index.html @@ -10,7 +10,7 @@ - + @@ -27,7 +27,7 @@ - + @@ -365,7 +365,7 @@ window.toggleNavTree = function(treeId, chevronId) { - + diff --git a/static/js/rehearser.js b/static/js/rehearser.js index 54d36dd..391496e 100644 --- a/static/js/rehearser.js +++ b/static/js/rehearser.js @@ -1880,9 +1880,24 @@ $('reh-autodesign-btn')?.addEventListener('click', async () => { // 2) For each character, design + save a voice let done = 0; - const designOnly = speakers.filter(sp => rehState.cast[sp].voice !== 'me'); + // rehState.cast[sp] used to be assumed always present since `speakers` came + // from Object.keys(rehState.cast) moments earlier — but the analyze-characters + // await above can take many seconds for a large cast, and if anything + // removes/replaces a cast entry while that request is in flight, the plain + // `rehState.cast[sp].voice` access below threw a TypeError with nothing + // catching it (this whole loop sits outside the try/catch above), silently + // killing the click with no toast and no further requests — exactly the + // "clicked Design all and nothing happens" symptom, confirmed by server + // logs showing analyze-characters succeeding but voice-design never once + // being called. Guard it and keep going instead of crashing the whole run. + // The outer try/finally below is the same idea one level up: ANY unexpected + // exception here used to vanish into the console with the button stuck + // disabled — now it surfaces as a toast and always resets the UI. + try { + const designOnly = speakers.filter(sp => rehState.cast[sp]?.voice !== 'me'); for (const sp of designOnly) { if (rehDesignCancelled) { toast('Cancelled', 'error'); break; } + if (!rehState.cast[sp]) { done++; continue; } const info = byName[sp.toUpperCase().trim()] || {}; const gender = (info.gender || 'N').toUpperCase().charAt(0).replace(/[^MFN]/, 'N') || 'N'; const desc = info.description || @@ -1948,11 +1963,15 @@ $('reh-autodesign-btn')?.addEventListener('click', async () => { // Refresh the broader voice library so avatars/pictures resolve if (typeof loadVoiceLibrary === 'function') await loadVoiceLibrary().catch(()=>{}); - renderCastList(); - populateNarratorSelect(); - if (prog) prog.hidden = true; - btn.disabled = false; if (!rehDesignCancelled) toast(`Designed ${done} voice${done!==1?'s':''} — tagged "${tag}" + Rehearser`, 'success'); + } catch (e) { + toast('Design all failed: ' + (e?.message || e), 'error'); + } finally { + renderCastList(); + populateNarratorSelect(); + if (prog) prog.hidden = true; + btn.disabled = false; + } }); // ── Match from library — LLM picks the best EXISTING voice for each character ──