refactor: publish feature APIs through TTVC

This commit is contained in:
mARTin-B78 2026-09-04 11:00:22 +02:00
parent e2e20fa89f
commit ed3b2d42cd
10 changed files with 30 additions and 4 deletions

View File

@ -8,6 +8,8 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
## [Unreleased]
### Changed
- **Namespace migration surface.** Extracted Rehearser and Audiobook modules now publish parse, stage, voice-picker,
and persistence APIs through `window.TTVC` while retaining legacy globals for the ongoing migration.
- **CI frontend gate.** The manifest-driven JavaScript syntax and top-level declaration collision check now runs in the
SonarQube quality workflow, alongside Python compilation, regressions, and the production bundle build.
- **Structured request correlation.** API and MCP requests now receive a bounded `X-Request-ID` and emit sanitized

View File

@ -93,6 +93,7 @@ architecture change: the larger Audiobook/Rehearser/style split.
duration logs; route-specific logs retain provider/job context without request bodies.
- [x] **L — Add linting and duplicate-function checks.** The manifest-driven frontend syntax/declaration gate now runs
locally and in the SonarQube quality workflow alongside the existing Python and bundle checks.
- [x] Expose extracted Rehearser and Audiobook parser APIs through the `window.TTVC` namespace bridge.
### P2 — after behavior and safety stabilize

View File

@ -1 +1 @@
1.24.29
1.24.30

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.29">
<meta name="app-version" content="1.24.30">
<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.24.29">
<link rel="stylesheet" href="/static/style.css?v=1.24.30">
<!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── -->
@ -392,7 +392,7 @@ window.toggleNavTree = function(treeId, chevronId) {
</script>
<!-- loader.js: fetches sections → loads JS modules → removes skeleton -->
<script src="/static/loader.js?v=1.24.29"></script>
<script src="/static/loader.js?v=1.24.30"></script>
</body>
</html>

View File

@ -23,3 +23,7 @@ const AB_PERSON_NOUNS = new Set(['Mann','Frau','Junge','Mädchen','Alte','Alter'
'Bauer','Priester','Priesterin','Nachbar','Nachbarin','Sklave','Sklavin','Verweser','Inquisitor','General','König','Königin',
'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']);
window.TTVC = window.TTVC || {};
window.TTVC.audiobook = window.TTVC.audiobook || {};
window.TTVC.audiobook.parse = { audiobookHasDialogue, audiobookDehyphenate };

View File

@ -57,3 +57,6 @@ function _rehIdbGetAll() {
} catch (e) { console.warn('[rehearser] migration skipped:', e); }
})();
window.rehDbGetById = rehDbGetById;
window.TTVC = window.TTVC || {};
window.TTVC.rehearser = window.TTVC.rehearser || {};
window.TTVC.rehearser.library = { rehDbGetAll, rehDbGetById, rehDbPut, rehDbDelete };

View File

@ -192,3 +192,6 @@ function detectCharacters(lines) {
return cast;
}
window.TTVC = window.TTVC || {};
window.TTVC.rehearser = window.TTVC.rehearser || {};
window.TTVC.rehearser.parse = { parseScript, detectCharacters };

View File

@ -99,3 +99,7 @@ function renderCastStrip() {
$('reh-font-inc')?.addEventListener('click', () => rehStageFontStep(0.1));
$('reh-font-dec')?.addEventListener('click', () => rehStageFontStep(-0.1));
$('reh-cast-toggle')?.addEventListener('click', rehToggleCast);
window.TTVC = window.TTVC || {};
window.TTVC.rehearser = window.TTVC.rehearser || {};
window.TTVC.rehearser.stage = { renderCastStrip, rehApplyStageFont, rehApplyCastCollapsed };

View File

@ -71,3 +71,7 @@ function castOnlinePanelHtml(sp, c) {
</div>
</div>`;
}
window.TTVC = window.TTVC || {};
window.TTVC.rehearser = window.TTVC.rehearser || {};
window.TTVC.rehearser.voicePicker = { castOnlinePanelHtml, preview: _rehAudition, stopPreview: _rehStopAudition };

View File

@ -31,6 +31,11 @@ test('parses a screenplay and builds the cast', async ({ page }) => {
await page.waitForSelector('#app-skeleton', { state: 'detached', timeout: 15_000 });
await navTo(page, 's-rehearser');
await page.waitForFunction(() => typeof window.navRehearserPhase === 'function', null, { timeout: 15_000 });
await expect.poll(() => page.evaluate(() => Boolean(
window.TTVC?.rehearser?.parse?.parseScript &&
window.TTVC?.rehearser?.stage?.renderCastStrip &&
window.TTVC?.rehearser?.voicePicker?.castOnlinePanelHtml
))).toBe(true);
await page.evaluate(() => window.navRehearserPhase(1));
// Go to the Cast tab area: paste a script and parse