test: preserve dialogue-only recast merges

This commit is contained in:
mARTin-B78 2026-09-04 15:31:45 +02:00
parent b6f4a9518b
commit 29f5243f6b
6 changed files with 41 additions and 10 deletions

View File

@ -8,6 +8,8 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
## [Unreleased]
### Changed
- **Dialogue-only merge regression coverage.** Recast/verify normalization now has explicit browser coverage proving that
only adjacent known-speaker dialogue merges; narration and unknown-speaker lines remain separate.
- **Visual identity regression coverage.** Portrait and concept-sheet prompt tests now verify shared identity anchors;
completed pairs centralize prompt-version, identity-fingerprint, and asset-digest metadata.
- **Frontend namespace migration completed.** The remaining utility, library, cast-compatibility, i18n, and settings/log

View File

@ -268,7 +268,7 @@ codebase; benefit is relative user/reliability impact.
- [x] **P1 / M — High benefit — Identity-anchor regression tests.** Add automated coverage proving avatar and concept-sheet
requests share canonical identity anchors and persist the prompt version used for each generated asset.
- [ ] **P1 / S–M — Medium benefit — Preserve dialogue-only merging.** Keep the dialogue-only merge for recast/verify
- [x] **P1 / S–M — Medium benefit — Preserve dialogue-only merging.** Keep the dialogue-only merge for recast/verify
outputs where adjacent pairs still occur, with regression coverage for the non-dialogue boundaries.
- [ ] **P1 / L–XL — Very high benefit — Rework segmentation from Run 3.** Inspect the earlier dialogue split/fusion
stages and correct the dominant unmatched-segment class using the measured Run 3 artifacts.

View File

@ -1 +1 @@
1.24.99
1.25.0

View File

@ -88,6 +88,7 @@ Done incrementally as global-scope ordered files (no ES-module change needed)
- ✅ All remaining application frontend modules are either privately scoped or intentionally stateless bootstrap/cache IIFEs;
legacy globals are explicit compatibility exports and new integrations use `window.TTVC` namespaces.
- ✅ Visual pair generation centralizes prompt-version, identity-fingerprint, and asset-digest metadata with regression coverage for shared anchors.
- ✅ Recast/verify dialogue merging has regression coverage for known-speaker-only behavior and non-dialogue boundaries.
- ✅ `static/css/resource-diagnostics.css` — isolated Performance/resource diagnostic panel styles.
- ✅ `static/css/background-jobs.css` — isolated background-job history and retry-panel styles.
- ✅ `static/css/voice-design.css` — isolated Voice Design form, samples, and preview-match styles.

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.99">
<meta name="app-version" content="1.25.0">
<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.99">
<link rel="stylesheet" href="/static/css/resource-diagnostics.css?v=1.24.99">
<link rel="stylesheet" href="/static/css/background-jobs.css?v=1.24.99">
<link rel="stylesheet" href="/static/css/voice-design.css?v=1.24.99">
<link rel="stylesheet" href="/static/css/rehearser.css?v=1.24.99">
<link rel="stylesheet" href="/static/css/audiobook.css?v=1.24.99">
<link rel="stylesheet" href="/static/style.css?v=1.25.0">
<link rel="stylesheet" href="/static/css/resource-diagnostics.css?v=1.25.0">
<link rel="stylesheet" href="/static/css/background-jobs.css?v=1.25.0">
<link rel="stylesheet" href="/static/css/voice-design.css?v=1.25.0">
<link rel="stylesheet" href="/static/css/rehearser.css?v=1.25.0">
<link rel="stylesheet" href="/static/css/audiobook.css?v=1.25.0">
<!-- ── 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.99"></script>
<script src="/static/loader.js?v=1.25.0"></script>
</body>
</html>

View File

@ -41,6 +41,34 @@ test('Audiobook lazy modules publish their namespace APIs after loading', async
}))).toEqual({ castView: 'function', stream: 'function', startCast: 'function', pipelineCast: 'function', core: 'function', preview: 'function', exportAudio: 'function' });
});
test('Audiobook recast merge preserves narration and merges only known dialogue pairs', async ({ page }) => {
await page.goto('/', { waitUntil: 'domcontentloaded' });
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.TTVC?.audiobook?.cast?.mergeAdjacentDialogue === 'function', null, { timeout: 15_000 });
await expect.poll(() => page.evaluate(() => {
const merge = window.TTVC.audiobook.cast.mergeAdjacentDialogue;
return merge([
{ type: 'narration', speaker: 'Narrator', text: 'A.' },
{ type: 'narration', speaker: 'Narrator', text: 'B.' },
{ type: 'dialogue', speaker: 'Mira', text: 'C.' },
{ type: 'dialogue', speaker: 'Mira', text: 'D.' },
{ type: 'dialogue', speaker: 'Unknown', text: 'E.' },
{ type: 'dialogue', speaker: 'Unknown', text: 'F.' },
]);
})).toEqual({
segments: [
{ type: 'narration', speaker: 'Narrator', text: 'A.' },
{ type: 'narration', speaker: 'Narrator', text: 'B.' },
{ type: 'dialogue', speaker: 'Mira', text: 'C.\n\nD.' },
{ type: 'dialogue', speaker: 'Unknown', text: 'E.' },
{ type: 'dialogue', speaker: 'Unknown', text: 'F.' },
],
merged: 1,
});
});
test('Rehearser playback module publishes transport APIs after loading', async ({ page }) => {
await page.goto('/', { waitUntil: 'domcontentloaded' });
await page.waitForFunction(() => typeof window.navTo === 'function', null, { timeout: 15_000 });