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] ## [Unreleased]
### Changed ### 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 - **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. 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 - **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. 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 - [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. 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 ### 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="format-detection" content="telephone=no">
<meta name="color-scheme" content="light dark"> <meta name="color-scheme" content="light dark">
<meta name="theme-color" content="#2563EB"> <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="manifest" href="/manifest.webmanifest">
<link rel="icon" href="/static/icon.svg" type="image/svg+xml"> <link rel="icon" href="/static/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/static/icon.svg"> <link rel="apple-touch-icon" href="/static/icon.svg">
@ -27,7 +27,7 @@
<!-- ── Core styles (local — no CDN dependency for first paint) ────────── --> <!-- ── Core styles (local — no CDN dependency for first paint) ────────── -->
<link rel="stylesheet" href="/static/vendor/mdi/materialdesignicons.min.css"> <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) ── --> <!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── -->
@ -392,7 +392,7 @@ window.toggleNavTree = function(treeId, chevronId) {
</script> </script>
<!-- loader.js: fetches sections → loads JS modules → removes skeleton --> <!-- 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> </body>
</html> </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', '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', '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']); '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); } } catch (e) { console.warn('[rehearser] migration skipped:', e); }
})(); })();
window.rehDbGetById = rehDbGetById; 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; 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-inc')?.addEventListener('click', () => rehStageFontStep(0.1));
$('reh-font-dec')?.addEventListener('click', () => rehStageFontStep(-0.1)); $('reh-font-dec')?.addEventListener('click', () => rehStageFontStep(-0.1));
$('reh-cast-toggle')?.addEventListener('click', rehToggleCast); $('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>
</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 page.waitForSelector('#app-skeleton', { state: 'detached', timeout: 15_000 });
await navTo(page, 's-rehearser'); await navTo(page, 's-rehearser');
await page.waitForFunction(() => typeof window.navRehearserPhase === 'function', null, { timeout: 15_000 }); 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)); await page.evaluate(() => window.navRehearserPhase(1));
// Go to the Cast tab area: paste a script and parse // Go to the Cast tab area: paste a script and parse