# 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 `