diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bd5586..1508d03 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.13.7] — 2026-07-06 + +### Fixed +- **"Recast selected" showed every character as queued, not just the ones picked** — the progress dialog looked exactly like a full recast because it was passed the entire roster as the target list; the selection only actually took effect at the final save step. Now only the picked character(s) go in as the target roster, so the dialog accurately shows just those as queued. +- **"Recast options" dropdown routinely opened off-screen** — its anchor button sits in the bottom action bar, so the menu opening downward (the default) was often pushed past the viewport edge, barely visible. It now opens upward when there isn't enough room below, and is styled more prominently. + +--- + ## [1.13.6] — 2026-07-06 ### Fixed diff --git a/VERSION b/VERSION index 2e3a551..b0f139e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.13.6 +1.13.7 diff --git a/static/index.html b/static/index.html index ff8892f..497a66a 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/audiobook.js b/static/js/audiobook.js index d2c312e..9afed87 100644 --- a/static/js/audiobook.js +++ b/static/js/audiobook.js @@ -1048,7 +1048,14 @@ function _abOpenRecastCharsMenu(anchorEl, bookTitle, existingChars) { ''; document.body.appendChild(menu); const rect = anchorEl.getBoundingClientRect(); - menu.style.top = (rect.bottom + 4) + 'px'; + // The anchor button sits in the panel's bottom action bar, so opening + // downward (the default) routinely pushed this near/past the viewport's + // bottom edge — barely visible, overlapping the page footer. Open upward + // instead whenever there isn't comfortably enough room below. + const menuH = 90; // ~2 rows, enough for this fixed 2-item menu + const openUp = rect.bottom + menuH + 12 > window.innerHeight; + menu.style.top = openUp ? '' : (rect.bottom + 4) + 'px'; + menu.style.bottom = openUp ? (window.innerHeight - rect.top + 4) + 'px' : ''; menu.style.left = Math.max(10, rect.right - 190) + 'px'; const close = () => { menu.remove(); document.removeEventListener('mousedown', onDoc); }; const onDoc = (e) => { if (!menu.contains(e.target) && e.target !== anchorEl) close(); }; diff --git a/static/js/character-sheets.js b/static/js/character-sheets.js index 02c9c50..c6c1824 100644 --- a/static/js/character-sheets.js +++ b/static/js/character-sheets.js @@ -788,8 +788,12 @@ async function csForReaderSelective(selectedNames) { if (!wanted.size) { toast('No characters selected', 'error'); return; } const text = csReaderText(); const book = readerState.title || 'Untitled book'; - const knownRoster = csKnownReaderRoster(); - const sheets = await csGenerate(text, null, knownRoster); + // Only the picked names go in as the target roster — passing the FULL + // roster here (like csForReader does) made the progress dialog show every + // character as "queued" even though just one or two were selected, which + // read as "it's recasting everyone" even though the save step at the end + // was already correctly filtered down to the picks. + const sheets = await csGenerate(text, null, selectedNames); if (!sheets) return; const filtered = sheets.filter(s => wanted.has(String(s.name || '').trim().toLowerCase())); if (!filtered.length) { toast('None of the selected characters turned up in this pass — try again or pick different ones', 'error'); return; } diff --git a/static/style.css b/static/style.css index 82d33bd..2b46245 100644 --- a/static/style.css +++ b/static/style.css @@ -5658,13 +5658,14 @@ code { background: var(--panel); border-radius: 4px; padding: 1px 5px; font-fami .ab-cv-castchars-group .ab-cv-castchars-menu { border-radius: 0 6px 6px 0; border-left: 0; padding: 0 8px; } .ab-recast-menu { position: fixed; z-index: 2001; background: var(--surface); border: 1px solid var(--border); - border-radius: 8px; box-shadow: 0 10px 25px rgba(0,0,0,.3); padding: 4px; min-width: 180px; - display: flex; flex-direction: column; gap: 1px; + border-radius: 8px; box-shadow: 0 10px 30px rgba(0,0,0,.4); padding: 5px; min-width: 200px; + display: flex; flex-direction: column; gap: 2px; } .ab-recast-menu button { - display: flex; align-items: center; gap: 8px; padding: 7px 10px; font-size: 12.5px; - border: none; background: none; color: var(--text); border-radius: 5px; cursor: pointer; text-align: left; + display: flex; align-items: center; gap: 9px; padding: 9px 11px; font-size: 13px; font-weight: 600; + border: none; background: none; color: var(--text); border-radius: 6px; cursor: pointer; text-align: left; } +.ab-recast-menu button .mdi { font-size: 16px; color: var(--accent); } .ab-recast-menu button:hover { background: var(--panel); } .ab-recast-select-list { max-height: 280px; overflow-y: auto; margin: 10px 0; text-align: left; border: 1px solid var(--border); border-radius: 6px; padding: 4px; } .ab-recast-select-list label { display: flex; align-items: center; gap: 8px; padding: 5px 6px; cursor: pointer; font-size: 13px; border-radius: 4px; }