From 66c5dd6e0c131c949290be0cea7403ae4ea078cf Mon Sep 17 00:00:00 2001 From: mARTin-B78 Date: Fri, 4 Sep 2026 12:37:37 +0200 Subject: [PATCH] refactor: scope audiobook core --- CHANGELOG.md | 4 ++++ VERSION | 2 +- static/index.html | 16 ++++++------- static/js/audiobook-parse.js | 9 ++++++++ static/js/audiobook.js | 44 ++++++++++++++++++++++++++++++++++++ tests/lazy-loading.spec.js | 5 ++-- 6 files changed, 69 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c350dae..1b6f78d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/VERSION b/VERSION index 90c2a29..e17883e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.24.62 +1.24.64 diff --git a/static/index.html b/static/index.html index 123fed1..9cec7f6 100644 --- a/static/index.html +++ b/static/index.html @@ -10,7 +10,7 @@ - + @@ -27,12 +27,12 @@ - - - - - - + + + + + + @@ -397,7 +397,7 @@ window.toggleNavTree = function(treeId, chevronId) { - + diff --git a/static/js/audiobook-parse.js b/static/js/audiobook-parse.js index b036530..d3ea9d4 100644 --- a/static/js/audiobook-parse.js +++ b/static/js/audiobook-parse.js @@ -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 || {}; diff --git a/static/js/audiobook.js b/static/js/audiobook.js index 2808b05..d9a748a 100644 --- a/static/js/audiobook.js +++ b/static/js/audiobook.js @@ -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); diff --git a/tests/lazy-loading.spec.js b/tests/lazy-loading.spec.js index 1a3f2b4..801e6b3 100644 --- a/tests/lazy-loading.spec.js +++ b/tests/lazy-loading.spec.js @@ -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 }) => {