Prefer the newest casting draft instead of always trusting localStorage (v1.20.22)
Opening a book always restored the localStorage draft and then pushed it to the server as a backfill, so a tab holding an older copy silently overwrote newer work saved from anywhere else — confirmed live: a cast improved from 79 Unknown lines to 25 was destroyed by opening the book in a stale tab. Local and server drafts are now compared by savedAt, the newest wins and is cached locally, and the server is only backfilled when the local copy is actually newer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
56800dab28
commit
3434293375
@ -22,6 +22,11 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
|
||||
- **Emotion instructions are now always written in English**, even for non-English voices (the spoken text and the native-accent clause stay in the book's own language). Confirmed by controlled A/B testing — same line, same voice, only the instruct language varying — that Qwen3-TTS follows English emotion instructions far more reliably: German instructs produced barely-differentiated output, while English instructs yield a clean, correctly-ordered prosodic gradient (whisper 128 Hz → sad 142 → neutral 179 → scared 203 → happy 225 → angry 269 Hz), with sensible duration changes too (sad slowest, scared fastest).
|
||||
- **Fish-Speech generation parameters (`temperature` / `top_p` / `repetition_penalty`) were never forwarded.** Every Fish-Speech line synthesized at the server's fixed defaults, ignoring the app's per-backend stability settings — the only backend not routed through the shared `_apply_tts_extra_params` helper.
|
||||
|
||||
## [1.20.22] — 2026-08-12
|
||||
|
||||
### Fixed
|
||||
- **A stale browser draft could silently destroy newer casting work.** On opening a book the localStorage draft always won and was then pushed to the server as a "backfill", so a tab holding an older copy overwrote whatever was stored server-side — confirmed live: a cast improved from 79 Unknown lines to 25 was wiped simply by opening the book in a tab that still held the old draft. The two copies are now compared by their save timestamp, the newest wins (and is cached locally), and the server is only backfilled when the local copy is genuinely the newer one.
|
||||
|
||||
## [1.20.21] — 2026-08-12
|
||||
|
||||
### Fixed
|
||||
|
||||
2
static/dist/main.min.js
vendored
2
static/dist/main.min.js
vendored
File diff suppressed because one or more lines are too long
@ -10,7 +10,7 @@
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<meta name="theme-color" content="#2563EB">
|
||||
<meta name="app-version" content="1.20.21">
|
||||
<meta name="app-version" content="1.20.22">
|
||||
<link rel="manifest" href="/manifest.webmanifest">
|
||||
<link rel="icon" href="/static/icon.svg" type="image/svg+xml">
|
||||
<link rel="apple-touch-icon" href="/static/icon.svg">
|
||||
@ -27,7 +27,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.20.21">
|
||||
<link rel="stylesheet" href="/static/style.css?v=1.20.22">
|
||||
|
||||
|
||||
<!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── -->
|
||||
@ -378,7 +378,7 @@ window.toggleNavTree = function(treeId, chevronId) {
|
||||
</script>
|
||||
|
||||
<!-- loader.js: fetches sections → loads JS modules → removes skeleton -->
|
||||
<script src="/static/loader.js?v=1.20.21"></script>
|
||||
<script src="/static/loader.js?v=1.20.22"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -4808,21 +4808,32 @@ async function audiobookOpenCastView() {
|
||||
view.complete(summary, audiobookShowPreview, audiobookCast, audiobookRecastUnknown);
|
||||
};
|
||||
|
||||
// 2. localStorage draft restore — fastest, no network
|
||||
// 2. localStorage draft restore — fastest, but NOT automatically authoritative.
|
||||
// The local copy used to win outright and was then pushed to the server as a
|
||||
// "backfill", which meant a stale browser draft silently overwrote newer work
|
||||
// saved from anywhere else — confirmed live: a cast improved from 79 Unknown
|
||||
// lines to 25 and stored server-side was destroyed simply by opening the book
|
||||
// in a tab holding the older draft. Whichever copy was saved most recently
|
||||
// wins, and the server is only backfilled when the local copy is genuinely
|
||||
// newer.
|
||||
const _localDraft = _abLoadDraft(text);
|
||||
if (_localDraft && _localDraft.segments && _localDraft.segments.length > 0) {
|
||||
const view = audiobookCastView(chunks.length, llm_url, model, false);
|
||||
_applyDraft(_localDraft, view, 'local');
|
||||
// Backfill server in case it's missing this draft
|
||||
if (_abBookId()) {
|
||||
const serverCopy = {
|
||||
..._localDraft,
|
||||
bookId: _abBookId(),
|
||||
title: window.readerState?.title || _localDraft.title || '',
|
||||
};
|
||||
fetch(`/api/reader/docs/${encodeURIComponent(_abBookId())}/scripts/cast`, {
|
||||
const bookId = _abBookId();
|
||||
let chosen = _localDraft, source = 'local', serverDraft = null;
|
||||
if (bookId) {
|
||||
try { serverDraft = await _abLoadDraftServer(bookId); } catch (_) {}
|
||||
if (serverDraft && (serverDraft.savedAt || 0) > (_localDraft.savedAt || 0)) {
|
||||
chosen = serverDraft; source = 'server';
|
||||
try { localStorage.setItem(_abDraftKey(bookId), JSON.stringify(serverDraft)); } catch (_) {}
|
||||
}
|
||||
}
|
||||
_applyDraft(chosen, view, source);
|
||||
// Only push upwards when the local copy really is the newer one.
|
||||
if (bookId && source === 'local' && (!serverDraft || (_localDraft.savedAt || 0) > (serverDraft.savedAt || 0))) {
|
||||
fetch(`/api/reader/docs/${encodeURIComponent(bookId)}/scripts/cast`, {
|
||||
method: 'PUT', headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify(serverCopy)
|
||||
body: JSON.stringify({ ..._localDraft, bookId, title: window.readerState?.title || _localDraft.title || '' })
|
||||
}).catch(() => {});
|
||||
}
|
||||
return;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user