refactor: scope audiobook core

This commit is contained in:
mARTin-B78 2026-09-04 12:37:37 +02:00
parent c6db55e55b
commit 66c5dd6e0c
6 changed files with 69 additions and 11 deletions

View File

@ -8,6 +8,10 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
## [Unreleased]
### Changed
- **Audiobook parser compatibility boundary.** Speech-heuristic constants consumed by the scoped Audiobook core are now
exported explicitly, preventing load-order regressions while preserving the parser namespace.
- **Audiobook core namespace migration.** Reader-side audiobook state, attribution helpers, quote controls, and Rehearser
handoff now use a private scope with explicit compatibility exports and a regression-tested core namespace.
- **Rehearser core namespace migration.** Script state, pagination, cast-management, and summary APIs now live inside a
private core scope with explicit legacy exports and a regression-tested `window.TTVC.rehearser.core` surface.
- **Rehearser recording namespace migration.** Microphone overlay, recording controls, and shared microphone errors now

View File

@ -1 +1 @@
1.24.62
1.24.64

View File

@ -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.24.62">
<meta name="app-version" content="1.24.64">
<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,12 +27,12 @@
<!-- ── 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.24.62">
<link rel="stylesheet" href="/static/css/resource-diagnostics.css?v=1.24.62">
<link rel="stylesheet" href="/static/css/background-jobs.css?v=1.24.62">
<link rel="stylesheet" href="/static/css/voice-design.css?v=1.24.62">
<link rel="stylesheet" href="/static/css/rehearser.css?v=1.24.62">
<link rel="stylesheet" href="/static/css/audiobook.css?v=1.24.62">
<link rel="stylesheet" href="/static/style.css?v=1.24.64">
<link rel="stylesheet" href="/static/css/resource-diagnostics.css?v=1.24.64">
<link rel="stylesheet" href="/static/css/background-jobs.css?v=1.24.64">
<link rel="stylesheet" href="/static/css/voice-design.css?v=1.24.64">
<link rel="stylesheet" href="/static/css/rehearser.css?v=1.24.64">
<link rel="stylesheet" href="/static/css/audiobook.css?v=1.24.64">
<!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── -->
@ -397,7 +397,7 @@ window.toggleNavTree = function(treeId, chevronId) {
</script>
<!-- loader.js: fetches sections → loads JS modules → removes skeleton -->
<script src="/static/loader.js?v=1.24.62"></script>
<script src="/static/loader.js?v=1.24.64"></script>
</body>
</html>

View File

@ -25,6 +25,15 @@ const AB_PERSON_NOUNS = new Set(['Mann','Frau','Junge','Mädchen','Alte','Alter'
'Prinz','Prinzessin','Fürst','Fürstin','Baron','Baronin','Bote','Diener','Dienerin','Knabe','Kind','Reiter','Reiterin',
'Bogenschütze','Schmied','Schmiedin','Heiler','Heilerin','Gelehrte','Gelehrter','Kapitän','Anführer','Anführerin']);
// Legacy audiobook.js still consumes these parser constants directly while
// the implementation is migrated to the namespace boundary.
Object.assign(root, {
AB_DIALOGUE_RE,
AB_SPEECH_VERBS,
AB_NOTNAME,
_AB_NAME,
AB_PERSON_NOUNS,
});
root.audiobookHasDialogue = audiobookHasDialogue;
root.audiobookDehyphenate = audiobookDehyphenate;
root.TTVC = root.TTVC || {};

View File

@ -1,4 +1,6 @@
// ── Book → multi-speaker audiobook ──────────────────────────────────────────
(function (root) {
//
// Bridges the Read Aloud reader and the Script Rehearser: an LLM scans the
// document (in the current scope — selection / page range / whole book),
@ -1738,3 +1740,45 @@ function audiobookExportDialog() {
modal.style.display = 'flex';
}
}
// Keep existing inline handlers and extracted modules working while the Reader
// bridge adopts the namespaced API responsibility by responsibility.
Object.assign(root, {
AUDIOBOOK_CHUNK_CHARS, AUDIOBOOK_WARMUP_TIMEOUT_MS, AUDIOBOOK_ATTRIBUTION_TIMEOUT_MS,
AUDIOBOOK_ATTRIBUTION_RETRY_TIMEOUT_MS, AUDIOBOOK_RECAST_TIMEOUT_MS,
AUDIOBOOK_RECAST_CONTEXT_CHARS, AUDIOBOOK_RECAST_TARGETS_PER_CALL, AUDIOBOOK_DRAFT_AUTOSAVE_MS,
_audiobook, _abHideCastingControl, _abDraftAutosaveTimer, _abLastServerDraftErrorAt,
audiobookDialogueKey, audiobookSegmentText, _abWordRangeAtPoint, audiobookJoinSegmentText,
_AB_INQUIT_VERBS, _abSuspectNarration, audiobookRecastGroups, audiobookRecastContext,
audiobookRecastRecent, audiobookFindReturnedSegment, audiobookFindReturnedSegmentSequence,
_abStr, _AB_DRAFT_KEY, _abBookId, _abDraftKey, _abTextId, _abSafeFilename, _abLastDraftSaveAt,
AB_DRAFT_SAVE_THROTTLE_MS, _abSaveDraft, _abExportBusy, audiobookExportCastMd,
_abStartDraftAutosave, _abStopDraftAutosave, _abEnsureLibraryBook, _abLoadDraftServer,
_abLoadDraft, _abClearDraft, audiobookResolveUnknowns, _AB_TAG_NAME, AB_SPEECH_TAG_ONLY_RE,
audiobookIsSpeechTagOnly, audiobookGuessSpeaker, AB_QUOTE_PAIRS, _AB_UNCLOSED_TAG_RE,
_abUnclosedQuoteEnd, _abOrphanClosingQuoteSpan, AB_QUOTE_OPENERS, AB_QUOTE_CLOSERS,
_audiobookFixOrphanedQuoteMarks, _abPrefixDupCut, _audiobookResolveRoleAliases,
_AB_GENERIC_ROLE_WORDS, _abIsGenericRoleLabel, _abSegmentConfidence, _AB_CONF_BANDS,
_abCalibrateConfidence, _abCheckBtnHtml, _abConfClass, _abConfBadgeHtml, _abStampConfidence,
audiobookIsRouterModel, audiobookSafeLlmModel, audiobookLlmUrl, audiobookLlmModel,
audiobookLang, audiobookAttributeBody, audiobookCurrentCastLlm, audiobookSaveLlmChoice,
audiobookScopeText, audiobookProgress, _abNormalizeColor, _abDefaultCharacterColor,
_abRecordColor, _abShowQuotes, _abDisplayText, _abMarkQuotes, audiobookToggleQuotes,
highlightText, _abShowBusyOverlay, _abFootMenuEl, _abCloseFootMenu, _abFootMenuOutside,
_abFootMenuKey, _abToggleFootMenu, _abOpenRecastSelectPopup, audiobookRecastSelectedCharacters,
_abScrollIntoView, _audiobookBuildRehRecord, _abRehSaveTimer, audiobookSaveAsRehearsal,
_audiobookDebouncedSave, audiobookExportDialog,
});
root.TTVC = root.TTVC || {};
root.TTVC.audiobook = root.TTVC.audiobook || {};
Object.assign(root.TTVC.audiobook, {
state: _audiobook,
core: {
dialogueKey: audiobookDialogueKey,
segmentText: audiobookSegmentText,
resolveUnknowns: audiobookResolveUnknowns,
toggleQuotes: audiobookToggleQuotes,
saveAsRehearsal: audiobookSaveAsRehearsal,
},
});
})(window);

View File

@ -29,15 +29,16 @@ test('Audiobook lazy modules publish their namespace APIs after loading', async
await page.waitForFunction(() => typeof window.navTo === 'function', null, { timeout: 15_000 });
await page.waitForSelector('#app-skeleton', { state: 'detached', timeout: 15_000 });
await page.evaluate(() => window.navTo('s-reader'));
await page.waitForFunction(() => typeof window.audiobookCastView === 'function', null, { timeout: 15_000 });
await page.waitForFunction(() => typeof window.TTVC?.audiobook?.core?.dialogueKey === 'function', null, { timeout: 15_000 });
await expect.poll(() => page.evaluate(() => ({
castView: typeof window.TTVC?.audiobook?.cast?.castView,
stream: typeof window.TTVC?.audiobook?.attributeStream,
startCast: typeof window.TTVC?.audiobook?.startCast,
pipelineCast: typeof window.TTVC?.audiobook?.pipeline?.cast,
core: typeof window.TTVC?.audiobook?.core?.dialogueKey,
preview: typeof window.TTVC?.audiobook?.preview?.show,
exportAudio: typeof window.TTVC?.audiobook?.exporting?.export,
}))).toEqual({ castView: 'function', stream: 'function', startCast: 'function', pipelineCast: 'function', preview: 'function', exportAudio: 'function' });
}))).toEqual({ castView: 'function', stream: 'function', startCast: 'function', pipelineCast: 'function', core: 'function', preview: 'function', exportAudio: 'function' });
});
test('Rehearser playback module publishes transport APIs after loading', async ({ page }) => {