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]
|
## [Unreleased]
|
||||||
|
|
||||||
### Changed
|
### 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
|
- **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.
|
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
|
- **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
|
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.
|
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`.
|
- [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
|
- [ ] Replace broad globals with namespaces such as `window.TTVC.audiobook`; pure leaf modules now use private scopes
|
||||||
exposes the existing Audiobook, Rehearser, and job APIs while responsibility-by-responsibility migration continues.
|
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;
|
- [x] Generate the feature-module list for `loader.js` and `scripts/minify.mjs` from one shared manifest;
|
||||||
larger responsibility splits remain.
|
larger responsibility splits remain.
|
||||||
- [x] Add a dependency-free frontend syntax and top-level declaration collision gate (`npm run check:frontend`);
|
- [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
|
### 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**:
|
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.
|
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.
|
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.
|
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.
|
**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
|
## 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
|
- 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.
|
legacy globals available while the `window.TTVC` surface is adopted.
|
||||||
- Replace remaining silent `.catch(() => {})` with `logErr(context, e)` (helper added in `utils.js`).
|
- 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="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.56">
|
<meta name="app-version" content="1.24.57">
|
||||||
<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,12 +27,12 @@
|
|||||||
|
|
||||||
<!-- ── 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.56">
|
<link rel="stylesheet" href="/static/style.css?v=1.24.57">
|
||||||
<link rel="stylesheet" href="/static/css/resource-diagnostics.css?v=1.24.56">
|
<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.56">
|
<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.56">
|
<link rel="stylesheet" href="/static/css/voice-design.css?v=1.24.57">
|
||||||
<link rel="stylesheet" href="/static/css/rehearser.css?v=1.24.56">
|
<link rel="stylesheet" href="/static/css/rehearser.css?v=1.24.57">
|
||||||
<link rel="stylesheet" href="/static/css/audiobook.css?v=1.24.56">
|
<link rel="stylesheet" href="/static/css/audiobook.css?v=1.24.57">
|
||||||
|
|
||||||
|
|
||||||
<!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── -->
|
<!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── -->
|
||||||
@ -397,7 +397,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.56"></script>
|
<script src="/static/loader.js?v=1.24.57"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
// Kept dependency-free so the Reader can load these helpers before the larger
|
// Kept dependency-free so the Reader can load these helpers before the larger
|
||||||
// audiobook casting workflow. The casting pipeline continues to use the same
|
// audiobook casting workflow. The casting pipeline continues to use the same
|
||||||
// legacy global function names during the incremental migration.
|
// legacy global function names during the incremental migration.
|
||||||
|
(function (root) {
|
||||||
|
|
||||||
const AB_DIALOGUE_RE = /[«»„“”"‟‚‘’『「<]|(?:^|\n)\s*[—–]\s/;
|
const AB_DIALOGUE_RE = /[«»„“”"‟‚‘’『「<]|(?:^|\n)\s*[—–]\s/;
|
||||||
function audiobookHasDialogue(t) { return AB_DIALOGUE_RE.test(t || ''); }
|
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',
|
'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 || {};
|
root.audiobookHasDialogue = audiobookHasDialogue;
|
||||||
window.TTVC.audiobook = window.TTVC.audiobook || {};
|
root.audiobookDehyphenate = audiobookDehyphenate;
|
||||||
window.TTVC.audiobook.parse = { audiobookHasDialogue, 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=window.TTVC||{};
|
||||||
window.TTVC.audiobook=window.TTVC.audiobook||{};
|
window.TTVC.audiobook=window.TTVC.audiobook||{};
|
||||||
Object.assign(window.TTVC.audiobook,{
|
Object.assign(window.TTVC.audiobook,{
|
||||||
|
startCast:audiobookCast,
|
||||||
|
openCastView:audiobookOpenCastView,
|
||||||
|
recastUnknown:audiobookRecastUnknown,
|
||||||
pipeline:{
|
pipeline:{
|
||||||
recastUnknown:audiobookRecastUnknown,
|
recastUnknown:audiobookRecastUnknown,
|
||||||
cast:audiobookCast,
|
cast:audiobookCast,
|
||||||
@ -1070,4 +1073,3 @@ Object.assign(window.TTVC.audiobook,{
|
|||||||
autoRepair:audiobookAutoRepairCast,
|
autoRepair:audiobookAutoRepairCast,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
// Quote-boundary segmentation and conservative turn-taking are kept separate
|
// Quote-boundary segmentation and conservative turn-taking are kept separate
|
||||||
// from the live LLM casting workflow. Their shared quote/heuristic helpers are
|
// from the live LLM casting workflow. Their shared quote/heuristic helpers are
|
||||||
// resolved when a passage is processed, preserving the legacy global API.
|
// resolved when a passage is processed, preserving the legacy global API.
|
||||||
|
(function (root) {
|
||||||
|
|
||||||
function _abImplausibleDialoguePiece(text) {
|
function _abImplausibleDialoguePiece(text) {
|
||||||
const t = String(text || '').trim();
|
const t = String(text || '').trim();
|
||||||
@ -58,6 +59,10 @@ function audiobookTurnTaking(segs) {
|
|||||||
return segs;
|
return segs;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.TTVC = window.TTVC || {};
|
root._abImplausibleDialoguePiece = _abImplausibleDialoguePiece;
|
||||||
window.TTVC.audiobook = window.TTVC.audiobook || {};
|
root.audiobookSplitByQuotes = audiobookSplitByQuotes;
|
||||||
window.TTVC.audiobook.segmentation = { splitByQuotes: audiobookSplitByQuotes, turnTaking: audiobookTurnTaking };
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
window.audiobookRecastSelectedCharacters = audiobookRecastSelectedCharacters;
|
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
|
// 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.
|
// that fills up as speakers are discovered. Far clearer than a bare bar.
|
||||||
@ -1735,14 +1738,3 @@ function audiobookExportDialog() {
|
|||||||
modal.style.display = 'flex';
|
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) ──
|
// ── Script parser (extracted from rehearser.js — global scope, load before it) ──
|
||||||
// Pure functions: parse screenplay/theatre text into typed lines, and derive the
|
// 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).
|
// initial cast. No load-time dependencies (SPEAKER_COLORS is read only at call time).
|
||||||
|
(function (root) {
|
||||||
function parseScript(text) {
|
function parseScript(text) {
|
||||||
const rawLines = text.split('\n');
|
const rawLines = text.split('\n');
|
||||||
const result = [];
|
const result = [];
|
||||||
@ -192,6 +193,9 @@ function detectCharacters(lines) {
|
|||||||
return cast;
|
return cast;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.TTVC = window.TTVC || {};
|
root.parseScript = parseScript;
|
||||||
window.TTVC.rehearser = window.TTVC.rehearser || {};
|
root.detectCharacters = detectCharacters;
|
||||||
window.TTVC.rehearser.parse = { parseScript, 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 ─────────────────────────────────
|
// ── Rehearser train-mode comparison helpers ─────────────────────────────────
|
||||||
// Recording/state-machine wiring remains in rehearser.js; these pure helpers
|
// Recording/state-machine wiring remains in rehearser.js; these pure helpers
|
||||||
// are isolated so transcript comparison can be tested and migrated first.
|
// are isolated so transcript comparison can be tested and migrated first.
|
||||||
|
(function (root) {
|
||||||
|
|
||||||
function _trainNormWords(str) {
|
function _trainNormWords(str) {
|
||||||
return (str || '').toLowerCase().replace(/[^a-zäöüàáâãèéêëìíîïòóôõùúûüýÿæœßа-яёА-ЯЁ0-9\s]/g, '').trim().split(/\s+/).filter(Boolean);
|
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 };
|
return { html_exp: expHtml, html_act: actHtml, score };
|
||||||
}
|
}
|
||||||
|
|
||||||
window.TTVC = window.TTVC || {};
|
root._trainCompare = _trainCompare;
|
||||||
window.TTVC.rehearser = window.TTVC.rehearser || {};
|
root.TTVC = root.TTVC || {};
|
||||||
window.TTVC.rehearser.train = { compare: _trainCompare };
|
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,
|
training: typeof window.TTVC?.rehearser?.training?.enter,
|
||||||
}))).toEqual({ start: 'function', stop: 'function', synthAll: 'function', loadRecord: 'function', exportSession: 'function', recording: 'function', training: 'function' });
|
}))).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