From 8922496ec90043616f48e09c31634d938467a012 Mon Sep 17 00:00:00 2001 From: mARTin-B78 Date: Sun, 5 Jul 2026 23:15:56 +0200 Subject: [PATCH] Fix silent Design-all crash in Script Rehearser (v1.12.99) Server logs showed /api/analyze-characters succeeding on every attempt but /api/voice-design never once being called - the bulk voice-design loop accessed rehState.cast[sp].voice unguarded right after that (multi-second, for a large cast) request resolved. Any cast-entry change during that window threw an uncaught TypeError, silently killing the whole run with no toast, no further requests, and the button stuck disabled. Guarded the access and wrapped the remaining flow in try/catch/finally so failures are visible and the UI always resets. Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 7 +++++++ VERSION | 2 +- static/index.html | 6 +++--- static/js/rehearser.js | 29 ++++++++++++++++++++++++----- 4 files changed, 35 insertions(+), 9 deletions(-) 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 ──