Reach without setup — and a safety net for every language. This release brings the English pipeline's polish to every other language Castwright performs, and takes the friction out of listening on your other devices. Non-English books — Russian, Spanish, French, German — now get a graceful fallback voice when a character's bespoke voice isn't ready yet, the same net English books have always had, with Coqui XTTS joining Qwen as an eligible casting engine. And listening from a phone or tablet now works the moment you install: LAN-over-HTTPS is the production default, provisions its own certificates, and keeps paired devices paired across upgrades — while the Pinokio one-click app finally surfaces an Open Web UI button the instant the server is ready. Around those two headliners, a run of accuracy and touch fixes: a stray name from a book's introduction no longer sneaks into your cast, the non-English quality gate understands numbers and contractions, designing a whole cast rides out GPU contention instead of grinding through it, you can audition a voice before assigning it, and tablet controls are finally finger-sized.
✨ Headline features
🌐 A fallback voice for every language — and Coqui as an eligible engine (new) — fs-60
Non-English books are no longer hard-locked to Qwen with no recovery path: an undesigned or briefly-unavailable character now falls back to a generic voice instead of stopping the chapter, exactly as English books already do — and Coqui XTTS becomes a first-class casting choice alongside it.
- fs-60: Coqui XTTS becomes an eligible casting choice and automatic fallback for Russian/Spanish/French/German books, no longer hard-locked to Qwen with no recovery path. A new
ENGINE_LANGUAGE_SUPPORT/resolveEligibleEnginesmodel (server) computes a per-bookeligibleTtsEnginesfield; an undesigned or unavailable Qwen character now falls back to a generic Coqui voice instead of failing the chapter, mirrored in the cast-view banner, the voice-readiness gate ("Proceed anyway" now names the real fallback engine), the engine picker, and a new "Fallback (Coqui)" status pill. A mixed Qwen+Coqui chapter is serialized (never co-resident) to protect an 8 GB card.PRELOAD_KOKORO's default also flipstrue→false(Coqui/Qwen already default off; Kokoro now matches). Live-GPU acceptance still owed. (#1005)
📱 Phone & tablet listening works out of the box — LAN HTTPS default
Reaching Castwright from another device on your network used to mean a special command and hand-generated certificates. Now every production install starts up ready — secure, device-locked, and pairing-capable — and degrades safely to loopback when certs can't be set up.
- LAN HTTPS is now the production default, so phone/tablet listening + companion pairing work out of the box — flipped safely.
isLanHttpsEnabled()defaults ON whenNODE_ENV=production(native installers, Pinokio,start:prod) and OFF in dev/test; explicitLAN_HTTPS=0/1still wins. Three coupled pieces make the flip safe: (1) boot never crashes on missing certs — the server computes effective LAN = requested AND certs-present and degrades to loopback HTTP with a one-command-fix warning instead of the oldprocess.exit(1); (2) the LAN API is never left unauthenticated —ensureLanAuthToken()auto-mints + persists aLAN_AUTH_TOKENto a shared, cross-release run-dir file (so a versioned upgrade keeps paired devices paired) whenever LAN is on and none is set (device tokens still accepted, loopback still bypasses, the pairing QR carries it), andGET /lan+ pairing advertise the actually-bound protocol/port so a cert-less degrade never hands out deadhttps://…:8443links; (3) certs auto-provision on every install path —setup-lan-certs.mjsis now non-fatal and honoursAPP_RUN_DIR,start-app-prod.mjsprovisions on first launch (native/manual/versioned), and Pinokioinstall.jsinstalls mkcert via conda + provisions. Each phone still installs the mkcert root CA once (pinned via the pairing QR's CA fingerprint). On-box pairing acceptance owed. (See docs/features/250-lan-https-default.md; the Pinokio Open-Web-UI tab under LAN also relies on the URL-capture fix below.) (#1541)
🗣️ Quality & Accuracy
- ASR content-QA gate: non-English integer-spelling and contraction normalization for es/fr/de/ru (#1084). Real per-language
maxWercalibration against rendered audio remains a tracked follow-up. - A real third-party person named/quoted only in a non-story front-matter chapter is no longer rostered as a cast member (#1447). Follow-up to #1446's synthesis safety net: a critical essay or foreword that quotes a different real author (e.g. "Вступительная статья" about Radiy Pogodin) used to have stage-2 cast that person as a one-line speaker, which
fold-minor-cast'sproseTaggedcarve-out then preserved as an orphaned cast reference. A new pre-fold guard (stripThirdPartyFrontMatter) strips a character only when all of: attributed in exactly one chapter with< minLineslines, name+aliases absent from every other chapter body, and that chapter is front-matter-suspicious (a dedicated essay-title predicateisNonStoryEssayTitle, or positional front-region) AND confirmed non-story by Signal 1 (title) or a conservative optional analyzer classification (runNonStoryClassification, Signal 2). Stripped lines re-route to the narrator, so the essay stays narrated and genuine framed/walk-on characters are untouched. - Dialogue-structure attribution escalation: two low-priority srv-59 follow-ups. Each escalated conversation window in a chapter now keeps its own forensics artifact on disk instead of the last window overwriting every earlier one; and the "characters present" candidate list shown to the escalation model no longer surfaces
narratoras a suggested guess (it's still a valid answer, just noise as a candidate). (#1483)
🎙️ Voice design & casting
- "Design full cast" no longer silently grinds through GPU contention. If another job was using the GPU while a bulk cast design was running, every remaining character used to fail identically, one after another, with the progress pill misleadingly climbing toward 100% the whole time (even reading "0/16 · 94%" — zero characters designed, but nearly "complete"). The sidecar now recognizes this specific contention and triggers its existing self-recovery restart (matching how it already handles other transient failures); the job rides out a brief pause before halting with a clear message naming the cause and how far it got, instead of grinding through every remaining character. The progress pill's percent no longer counts failures as progress, and now shows the failure count inline while the job is still running. (#1533)
- fe-7: per-row voice sample-preview button in the model-voice override picker (#416). Each base-voice row in the Profile Drawer's override picker now carries a hover/focus-revealed ▶ (faintly visible on touch via
coarse-pointer:opacity-60) that auditions that voice through the sharedplayBaseVoiceSampleWithAutoLoadsingle-flight helper using the drawer'spreviewText, without committing the override; picking the row still commits.stopPropagationseparates audition from selection, and the button carriescoarse-pointer44px touch sizing. New wrapper tests invoice-override-picker.test.tsx.
🚀 One-click install (Pinokio)
- The Pinokio launcher now surfaces a proper way into the app during the 1–2 min startup, works under LAN sharing, and confirms Reset. Three launcher-UX fixes, matching the conventions of every shipping Pinokio app (comfy/flux-webui/dia/Kokoro-TTS): (1) while the server is warming up (
local.urlnot yet captured) the menu now defaults to a live Terminal tab and auto-flips to Open Web UI the instant the server is ready — previously it renderedOpen Web UIwith anullhref, so Pinokio fell back to a bare terminal and first-time users saw no way to open the app; (2)start.jsnow captures the URL the server actually prints (/(https?:\/\/localhost:[0-9]+)/→input.event[1]) instead of hardcodinghttp://localhost:8080, so LAN sharing (LAN_HTTPS=1, server bindshttps://localhost:8443) surfaces a working Open-Web-UI tab too; (3) the Reset menu item now carries aconfirm:before it wipesnode_modules/venv/distand reinstalls.menu.test.js(both running states + the Reset-confirm guard) andstructure.test.js(adds therunning/url:nullstartup window) pin all three. (Refs #822)
📱 Touch & tablet
- Library Cards/Table toggle fixed on touch + app-wide 44px touch targets for tablets (bug). Two defects surfaced via an Android tablet emulator + a Playwright coarse-pointer probe. (1) The library view toggle rendered unconditionally in
LibraryChromebutbook-library.tsxforce-collapses to cards below 640px — so tapping Table flippedaria-pressedyet never changed the view (a live-looking dead control at 600px:aria-pressed=true, table sections = 0). It's now hidden whenever the viewport forces card mode (newshowViewToggleprop, regression test inbook-library.test.tsx). (2) The touch-target convention flipped frommin-h-[44px] sm:min-h-0(phone-only —sm:stripped the 44px target at exactly the ≥640px tablet range) tomin-h-[44px] fine-pointer:min-h-0, so phones AND tablets get WCAG-2.5.5 targets while mouse (fine-pointer) devices stay compact; library + top-bar controls that lacked any base size (toggle, filter/tag/language pills, table kebab, theme toggle, account avatar, version + admin pills) gotcoarse-pointersizing. 44 files. Verified: toggle 44px at tablet width, hidden below 640px, all frontend tests + tsc green. (#1543)
Full changelog: v1.12.3...v1.13.0