refactor: add frontend namespace migration bridge

This commit is contained in:
mARTin-B78 2026-09-04 10:07:46 +02:00
parent c4a5689a1d
commit 55d0dd1012
7 changed files with 38 additions and 5 deletions

View File

@ -8,6 +8,8 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
## [Unreleased]
### Added
- **Frontend Namespace Bridge.** A stable `window.TTVC` migration surface now exposes Audiobook, Rehearser, and job
APIs while preserving legacy globals for compatibility during the larger module split.
- **Book 2 Attribution Generalization.** The full 1,663-segment Book 2 snapshot is now a regression; structural
`Narrator` labels can no longer become dialogue speakers or phantom cast members.
- **Retry UI Regression Coverage.** Desktop smoke tests now verify that a Retry button is rendered only when the

View File

@ -107,7 +107,8 @@ the first block before investing in architecture or polish.
## P1 — Modular frontend architecture
- [ ] Split `audiobook.js`, `rehearser.js`, and `style.css` by responsibility.
- [ ] Replace broad globals with namespaces such as `window.TTVC.audiobook`.
- [ ] Replace broad globals with namespaces such as `window.TTVC.audiobook`; a compatibility namespace bridge now
exposes the existing Audiobook, Rehearser, and job APIs while responsibility-by-responsibility migration continues.
- [x] Generate the feature-module list for `loader.js` and `scripts/minify.mjs` from one shared manifest;
larger responsibility splits remain.
- [x] Add a dependency-free frontend syntax and top-level declaration collision gate (`npm run check:frontend`);

View File

@ -1 +1 @@
1.24.11
1.24.12

File diff suppressed because one or more lines are too long

23
static/js/namespace.js Normal file
View File

@ -0,0 +1,23 @@
// Stable migration surface for the legacy shared-global frontend.
// Existing names remain available for compatibility; new code should prefer
// these namespaces while the large Audiobook/Rehearser files are split.
(function exposeTtvcNamespaces() {
const root = window.TTVC = window.TTVC || {};
root.audiobook = root.audiobook || {};
root.rehearser = root.rehearser || {};
root.jobs = root.jobs || {};
[
['audiobook', 'fetchWithTimeout', 'audiobookFetchWithTimeout'],
['audiobook', 'attributeStream', 'audiobookAttributeStream'],
['audiobook', 'timeoutSeconds', 'audiobookTimeoutSeconds'],
['rehearser', 'renderLibrary', 'renderLibraryList'],
['rehearser', 'showPhase', 'showPhase'],
].forEach(([namespace, publicName, legacyName]) => {
if (typeof window[legacyName] !== 'undefined' && root[namespace][publicName] == null) {
root[namespace][publicName] = window[legacyName];
}
});
// `rehState` is a top-level lexical binding for compatibility and therefore
// is not guaranteed to be an own property of window.
if (typeof rehState !== 'undefined' && root.rehearser.state == null) root.rehearser.state = rehState;
})();

View File

@ -2,5 +2,5 @@
"voice-picker", "benchmark-voice-picker", "voice-inspector", "seed-finder", "voice-sources", "fishaudio-browser", "jobs",
"integrations", "routing", "voice-clone", "voice-library", "tts-preview", "generation", "benchmark", "stt",
"rehearser-parse", "rehearser", "reader", "audiobook", "audiobook/network", "character-sheets",
"characters-library", "sillytavern", "library", "character-columns", "library-characters", "studio"
"characters-library", "sillytavern", "library", "character-columns", "library-characters", "studio", "namespace"
]

View File

@ -21,6 +21,7 @@ test('app shell loads without JS errors', async ({ page }) => {
await page.waitForTimeout(1500);
const real = errors.filter(e => /ReferenceError|TypeError|SyntaxError|is not defined|is not a function/.test(e));
expect(real, real.join('\n')).toHaveLength(0);
await expect.poll(() => page.evaluate(() => !!window.TTVC?.audiobook && !!window.TTVC?.rehearser)).toBeTruthy();
});
// Navigate via the app's global router (sidebar is hidden behind a menu on mobile,
@ -99,6 +100,12 @@ test('background job history exposes retry only when the server allows it', asyn
const jobs = page.locator('#background-jobs-list');
await expect(jobs).toContainText('Temporary provider failure');
await expect(jobs.getByRole('button', { name: 'Retry' })).toBeVisible();
await jobs.getByRole('button', { name: 'Retry' }).click();
const clicked = await page.evaluate(() => {
const button = document.querySelector('#background-jobs-list button');
if (!button) return false;
button.click();
return true;
});
expect(clicked).toBeTruthy();
await expect.poll(() => retryRequests).toBe(1);
});