Inline delivery hints within a narration line, wired into BOTH front doors so
Audiobook and Stories behave identically.
- services/ssml_lite.py (parallel-built, 18 tests): parse_ssml_lite splits a
line into {text, speed, spell, emphasis} segments — nesting (innermost wins),
unclosed-to-EOL, stray-close ignored, adjacent-merge; ReDoS-safe literal
alternation. + spell_out().
- _parse_spans (audiobook script path) now applies SSML-lite as the innermost
layer (precedence: [voice:] → [pause] → SSML); each segment becomes a Span
with its speed (threaded to the renderer) and spelled-out text for [spell].
Trailing pause attaches to the run's last segment.
- frontend/src/utils/ssmlLite.js: client port (kept in sync with the .py) +
storyToSpans applies it per chunk — inline speed OVERRIDES the per-line slider,
falls back to it otherwise.
Tests: parse_ssml_lite (18 py + 10 js), script-level prosody parse, Stories
SSML compile (override + spell). 70 backend + 345 frontend green; build clean.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>