tts-voice-creator-clone-and.../docs/ARCHITECTURE.md
mARTin-B78 40e42590cc Release v1.6.0: a11y (WCAG AA), i18n (DE), PWA, perf, tests, Cast UX
Cast: card/list views, sort & filter, online voice picker, "Hear a line"
sample button, AI character notes, import auto-save.

Platform: WCAG 2.1 AA accessibility pass; German UI translation + language
picker; installable PWA with offline shell; GZip + content-visibility
virtualization + lazy images + Rehearser PCM memory cap (mobile stability);
Playwright suite (desktop + iPhone); opt-in minified bundle build.

Fixes: screenplay parser false characters; Fish-Speech inline-tag tones;
narrator/voice pickers list full library; clone GUI rework; fish.audio
import dedup; voice-ID rename; bulk-delete modal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 14:23:35 +02:00

44 lines
4.3 KiB
Markdown

# Architecture & optimization notes
## Current shape
- **Backend**: FastAPI, modular — `server.py` (app + middleware) → `routes/*.py` (admin, tts, library, sources, stt, conversation, docker, settings) → `core/*.py` (audio, voice, config, validation, tts_helpers, …).
- **Frontend**: ~15k lines of **plain global-scope JS** in `static/js/*.js`, loaded in order by `static/loader.js` (no bundler/build step). Sections are HTML fragments in `static/sections/*.html` fetched at runtime. Modules communicate via globals (`window._voices`, `$`, `escHtml`, `toast`, `saveMeta`, `loadVoiceLibrary`, …) and some runtime monkey-patching (e.g. `renderVoiceList` is reassigned in `voice-library.js`).
## Already optimised
- **GZip** responses (`server.py`) — ~75% smaller text transfer.
- **Static caching** middleware (immutable for versioned JS/CSS).
- **Native virtualization**: `content-visibility:auto` on long-list rows (voice library, online cards, cast cards).
- **Lazy images** (`loading="lazy" decoding="async"`) across all big lists.
- **Rehearser memory cap**: decoded PCM is kept to a sliding window (`REH_DECODE_WINDOW`) around the playhead.
- **AudioContext** instances are closed (iOS limit).
- **PWA**: `manifest.webmanifest` + network-first `sw.js` (served from root for `/` scope), installable, offline shell.
- **Concurrency pool** (`runPool` in `utils.js`) for bulk network ops.
- **Smoke tests**: Playwright (`tests/`, desktop + iPhone profiles).
## Build tooling (opt-in) — DONE
`npm run minify``scripts/minify.mjs` concatenates the feature modules **in loader order** into `static/dist/main.min.js` and minifies whitespace/syntax only (**identifiers kept** — inline HTML `onclick="navTo(...)"` and cross-file refs rely on global names). `loader.js` loads this single bundle when `window.APP_USE_BUNDLE === true` (or `?bundle=1`), else falls back to per-file loading — so editing `static/js/*` stays live by default. Verified end-to-end by `tests/bundle.spec.js`. Rebuild the bundle after JS edits if you ship with it enabled.
## Staged migration plan (the two big refactors — do incrementally, behind the smoke tests)
### 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.
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.
### 2. Split `rehearser.js` (was 196 KB) — IN PROGRESS
Done incrementally as global-scope ordered files (no ES-module change needed) — each loaded before `rehearser.js` in `loader.js` + added to the bundle list in `scripts/minify.mjs`, verified by `tests/rehearser.spec.js` (parse → cast) and the smoke suite:
-`rehearser-parse.js``parseScript`, `detectCharacters` (pure; only call-time `SPEAKER_COLORS`).
-`rehearser-cast.js``renderCastList`, sort/filter, online voice picker, character research.
-`rehearser-stage.js` — playback, synth cache, pre-decode window, transport.
-`rehearser-train.js` — mic record / STT compare.
-`rehearser-library.js` — IndexedDB session store, import/export, auto-save.
**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
- Replace remaining silent `.catch(() => {})` with `logErr(context, e)` (helper added in `utils.js`).
- Full a11y sweep (icon-button `aria-label`s, focus traps, keyboard nav in `createSearchablePicker`) — run the `web-design-guidelines` review.
- If ever exposed beyond localhost: add an auth token + rate limiting (SSRF is already guarded in `core/validation.py`).