refactor: scope pure frontend leaves
This commit is contained in:
parent
d32985256d
commit
302dbe1279
@ -8,6 +8,8 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
- **Pure leaf namespace migration.** Rehearser parsing/training and Audiobook parsing/segmentation helpers now keep
|
||||
declarations private and publish explicit compatibility exports alongside their `window.TTVC` APIs.
|
||||
- **P1 architecture audit.** Marked the incremental frontend modularization complete: all planned Audiobook/Rehearser
|
||||
responsibility slices are manifest-driven, namespace-tested, and compatibility-preserving; full ES-module migration remains future work.
|
||||
- **Rehearser training extraction.** Training-mode state, cue playback, transcription, microphone recording, and phase
|
||||
|
||||
4
TODO.md
4
TODO.md
@ -124,8 +124,8 @@ architecture change: the larger Audiobook/Rehearser/style split.
|
||||
voice design, I/O, library, stage, and recording blocks plus Audiobook casting, preview, export, and feature styles
|
||||
are isolated. Core Reader state and compatibility event wiring remain ordered in the legacy entry points.
|
||||
- [x] Extract Rehearser stage display and cast-sidebar controls into `rehearser-stage.js`.
|
||||
- [ ] 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.
|
||||
- [ ] Replace broad globals with namespaces such as `window.TTVC.audiobook`; pure leaf modules now use private scopes
|
||||
with explicit compatibility exports, while stateful modules continue the responsibility-by-responsibility migration.
|
||||
- [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`);
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
### 1. Decouple globals → ES modules
|
||||
The global scope + monkey-patching is the root cause of the recurring "wiring/timing" bugs (controls bound before the section mounts, etc.). Migrate **one file at a time**:
|
||||
1. Add `"type": "module"` loading path; keep a compat shim that re-exposes the few cross-module symbols on `window` during transition.
|
||||
2. Convert leaf modules first (`utils`, `settings`, `voice-picker`), exporting explicitly and importing where used.
|
||||
2. Convert leaf modules first (pure Rehearser/Audiobook helpers are now privately scoped), exporting explicitly and importing where used.
|
||||
3. Replace the `renderVoiceList` monkey-patch with a normal function + an `onRender` hook.
|
||||
4. Switch `loader.js` to a single `<script type="module">` entry that imports the graph; `esbuild --bundle` then produces one minified, tree-shaken file.
|
||||
|
||||
@ -60,6 +60,8 @@ Done incrementally as global-scope ordered files (no ES-module change needed)
|
||||
**Pattern (proven):** extract a cohesive block whose only cross-file references resolve at *call time* (event handlers), not at module load. Top-level `const rehState` and top-level `addEventListener` registrations must stay ordered — extract those last and load them after their dependencies. Re-run `npx playwright test` after each extraction.
|
||||
|
||||
## Remaining recommendations
|
||||
- Complete the eventual ES-module/global-compatibility migration one leaf at a time; current behavior intentionally keeps
|
||||
legacy globals available while the `window.TTVC` surface is adopted.
|
||||
- Complete the eventual ES-module/global-compatibility migration one leaf at a time; current behavior intentionally keeps
|
||||
legacy globals available while the `window.TTVC` surface is adopted.
|
||||
- Replace remaining silent `.catch(() => {})` with `logErr(context, e)` (helper added in `utils.js`).
|
||||
|
||||
@ -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.56">
|
||||
<meta name="app-version" content="1.24.57">
|
||||
<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.56">
|
||||
<link rel="stylesheet" href="/static/css/resource-diagnostics.css?v=1.24.56">
|
||||
<link rel="stylesheet" href="/static/css/background-jobs.css?v=1.24.56">
|
||||
<link rel="stylesheet" href="/static/css/voice-design.css?v=1.24.56">
|
||||
<link rel="stylesheet" href="/static/css/rehearser.css?v=1.24.56">
|
||||
<link rel="stylesheet" href="/static/css/audiobook.css?v=1.24.56">
|
||||
<link rel="stylesheet" href="/static/style.css?v=1.24.57">
|
||||
<link rel="stylesheet" href="/static/css/resource-diagnostics.css?v=1.24.57">
|
||||
<link rel="stylesheet" href="/static/css/background-jobs.css?v=1.24.57">
|
||||
<link rel="stylesheet" href="/static/css/voice-design.css?v=1.24.57">
|
||||
<link rel="stylesheet" href="/static/css/rehearser.css?v=1.24.57">
|
||||
<link rel="stylesheet" href="/static/css/audiobook.css?v=1.24.57">
|
||||
|
||||
|
||||
<!-- ── 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.56"></script>
|
||||
<script src="/static/loader.js?v=1.24.57"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
// Kept dependency-free so the Reader can load these helpers before the larger
|
||||
// audiobook casting workflow. The casting pipeline continues to use the same
|
||||
// legacy global function names during the incremental migration.
|
||||
(function (root) {
|
||||
|
||||
const AB_DIALOGUE_RE = /[«»„“”"‟‚‘’『「<]|(?:^|\n)\s*[—–]\s/;
|
||||
function audiobookHasDialogue(t) { return AB_DIALOGUE_RE.test(t || ''); }
|
||||
@ -24,6 +25,9 @@ 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']);
|
||||
|
||||
window.TTVC = window.TTVC || {};
|
||||
window.TTVC.audiobook = window.TTVC.audiobook || {};
|
||||
window.TTVC.audiobook.parse = { audiobookHasDialogue, audiobookDehyphenate };
|
||||
root.audiobookHasDialogue = audiobookHasDialogue;
|
||||
root.audiobookDehyphenate = audiobookDehyphenate;
|
||||
root.TTVC = root.TTVC || {};
|
||||
root.TTVC.audiobook = root.TTVC.audiobook || {};
|
||||
root.TTVC.audiobook.parse = { audiobookHasDialogue, audiobookDehyphenate };
|
||||
})(window);
|
||||
|
||||
@ -1063,6 +1063,9 @@ async function audiobookCast(overrideUrl, overrideModel, resume) {
|
||||
window.TTVC=window.TTVC||{};
|
||||
window.TTVC.audiobook=window.TTVC.audiobook||{};
|
||||
Object.assign(window.TTVC.audiobook,{
|
||||
startCast:audiobookCast,
|
||||
openCastView:audiobookOpenCastView,
|
||||
recastUnknown:audiobookRecastUnknown,
|
||||
pipeline:{
|
||||
recastUnknown:audiobookRecastUnknown,
|
||||
cast:audiobookCast,
|
||||
@ -1070,4 +1073,3 @@ Object.assign(window.TTVC.audiobook,{
|
||||
autoRepair:audiobookAutoRepairCast,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
// Quote-boundary segmentation and conservative turn-taking are kept separate
|
||||
// from the live LLM casting workflow. Their shared quote/heuristic helpers are
|
||||
// resolved when a passage is processed, preserving the legacy global API.
|
||||
(function (root) {
|
||||
|
||||
function _abImplausibleDialoguePiece(text) {
|
||||
const t = String(text || '').trim();
|
||||
@ -58,6 +59,10 @@ function audiobookTurnTaking(segs) {
|
||||
return segs;
|
||||
}
|
||||
|
||||
window.TTVC = window.TTVC || {};
|
||||
window.TTVC.audiobook = window.TTVC.audiobook || {};
|
||||
window.TTVC.audiobook.segmentation = { splitByQuotes: audiobookSplitByQuotes, turnTaking: audiobookTurnTaking };
|
||||
root._abImplausibleDialoguePiece = _abImplausibleDialoguePiece;
|
||||
root.audiobookSplitByQuotes = audiobookSplitByQuotes;
|
||||
root.audiobookTurnTaking = audiobookTurnTaking;
|
||||
root.TTVC = root.TTVC || {};
|
||||
root.TTVC.audiobook = root.TTVC.audiobook || {};
|
||||
root.TTVC.audiobook.segmentation = { splitByQuotes: audiobookSplitByQuotes, turnTaking: audiobookTurnTaking };
|
||||
})(window);
|
||||
|
||||
@ -1548,6 +1548,9 @@ async function audiobookRecastSelectedCharacters(selectedNames) {
|
||||
return null;
|
||||
}
|
||||
window.audiobookRecastSelectedCharacters = audiobookRecastSelectedCharacters;
|
||||
window.TTVC = window.TTVC || {};
|
||||
window.TTVC.audiobook = window.TTVC.audiobook || {};
|
||||
window.TTVC.audiobook.recastSelectedCharacters = audiobookRecastSelectedCharacters;
|
||||
|
||||
// Live casting view: a scrolling feed of attributed lines + a character roster
|
||||
// that fills up as speakers are discovered. Far clearer than a bare bar.
|
||||
@ -1735,14 +1738,3 @@ function audiobookExportDialog() {
|
||||
modal.style.display = 'flex';
|
||||
}
|
||||
}
|
||||
|
||||
window.TTVC = window.TTVC || {};
|
||||
window.TTVC.audiobook = window.TTVC.audiobook || {};
|
||||
Object.assign(window.TTVC.audiobook, {
|
||||
startCast: audiobookCast,
|
||||
openCastView: audiobookOpenCastView,
|
||||
recastUnknown: audiobookRecastUnknown,
|
||||
recastSelectedCharacters: audiobookRecastSelectedCharacters,
|
||||
export: audiobookExport,
|
||||
saveAsRehearsal: audiobookSaveAsRehearsal,
|
||||
});
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// ── Script parser (extracted from rehearser.js — global scope, load before it) ──
|
||||
// Pure functions: parse screenplay/theatre text into typed lines, and derive the
|
||||
// initial cast. No load-time dependencies (SPEAKER_COLORS is read only at call time).
|
||||
(function (root) {
|
||||
function parseScript(text) {
|
||||
const rawLines = text.split('\n');
|
||||
const result = [];
|
||||
@ -192,6 +193,9 @@ function detectCharacters(lines) {
|
||||
return cast;
|
||||
}
|
||||
|
||||
window.TTVC = window.TTVC || {};
|
||||
window.TTVC.rehearser = window.TTVC.rehearser || {};
|
||||
window.TTVC.rehearser.parse = { parseScript, detectCharacters };
|
||||
root.parseScript = parseScript;
|
||||
root.detectCharacters = detectCharacters;
|
||||
root.TTVC = root.TTVC || {};
|
||||
root.TTVC.rehearser = root.TTVC.rehearser || {};
|
||||
root.TTVC.rehearser.parse = { parseScript, detectCharacters };
|
||||
})(window);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// ── Rehearser train-mode comparison helpers ─────────────────────────────────
|
||||
// Recording/state-machine wiring remains in rehearser.js; these pure helpers
|
||||
// are isolated so transcript comparison can be tested and migrated first.
|
||||
(function (root) {
|
||||
|
||||
function _trainNormWords(str) {
|
||||
return (str || '').toLowerCase().replace(/[^a-zäöüàáâãèéêëìíîïòóôõùúûüýÿæœßа-яёА-ЯЁ0-9\s]/g, '').trim().split(/\s+/).filter(Boolean);
|
||||
@ -36,6 +37,8 @@ function _trainCompare(expected, actual) {
|
||||
return { html_exp: expHtml, html_act: actHtml, score };
|
||||
}
|
||||
|
||||
window.TTVC = window.TTVC || {};
|
||||
window.TTVC.rehearser = window.TTVC.rehearser || {};
|
||||
window.TTVC.rehearser.train = { compare: _trainCompare };
|
||||
root._trainCompare = _trainCompare;
|
||||
root.TTVC = root.TTVC || {};
|
||||
root.TTVC.rehearser = root.TTVC.rehearser || {};
|
||||
root.TTVC.rehearser.train = { compare: _trainCompare };
|
||||
})(window);
|
||||
|
||||
@ -56,3 +56,17 @@ test('Rehearser playback module publishes transport APIs after loading', async (
|
||||
training: typeof window.TTVC?.rehearser?.training?.enter,
|
||||
}))).toEqual({ start: 'function', stop: 'function', synthAll: 'function', loadRecord: 'function', exportSession: 'function', recording: 'function', training: 'function' });
|
||||
});
|
||||
|
||||
test('pure leaf modules keep private declarations behind compatibility exports', 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.audiobookSplitByQuotes === 'function', null, { timeout: 15_000 });
|
||||
await expect.poll(() => page.evaluate(() => ({
|
||||
parse: typeof window.TTVC?.rehearser?.parse?.parseScript,
|
||||
compare: typeof window.TTVC?.rehearser?.train?.compare,
|
||||
dialogue: typeof window.TTVC?.audiobook?.parse?.audiobookHasDialogue,
|
||||
split: typeof window.TTVC?.audiobook?.segmentation?.splitByQuotes,
|
||||
}))).toEqual({ parse: 'function', compare: 'function', dialogue: 'function', split: 'function' });
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user