Add a "Highlight Current Sentence" keyboard action (default Shift+M, in the
Text to Speech shortcut section) that persists the sentence TTS is reading
aloud as a normal highlight using the user's default style/color — no text
selection, eyes-off, silent, and idempotent (a repeat press on the same
sentence is a no-op rather than a duplicate).
Flow: the shortcut handler in useBookShortcuts dispatches tts-highlight-sentence
→ useTTSControl (which owns the TTSController) resolves the current sentence via
the new TTSController.getSpokenSentence() and relays create-tts-highlight
→ Annotator builds the BookNote with the pure, unit-tested buildTTSSentenceHighlight
helper and persists/renders it like any other highlight.
Closes#4085
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the redundant "Apply also in Scrolled Mode" options for bars and
margins so scrolled mode renders the header/footer consistently with
paginated mode: transparent, fixed in position, and not obscuring content.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When TTS playback crosses a section boundary, the page would stay on
the last page of the previous chapter while audio continued reading
the next chapter — leaving the user stuck behind the "back-to-TTS"
button.
Two compounding issues since the paginator adjacent-section preloading
landed:
1. `handleSectionChange` called `view.renderer.goTo(resolved)` without
awaiting. `TTSController.#initTTSForSection` does
`await this.onSectionChange?.(sectionIndex)` precisely so the view
can finish navigating before audio of the new section starts, but
the missing await defeated that contract.
2. `handleHighlightMark` returned silently on a cross-section
mismatch (`viewSectionIndex !== ttsSectionIndex`), so when the
renderer.goTo above completed only partially — which can happen on
the new paginator when the target section is already loaded as an
adjacent view and the post-goTo state appears reused without a
visible page flip — there was no second chance to drag the view to
the TTS cfi.
Fix:
- Await `view.renderer.goTo` in `handleSectionChange`.
- In `handleHighlightMark`, run the cross-section branch *before* the
`followingTTSLocationRef` check and call `view.goTo(cfi)` directly,
stamping `sectionChangingTimestampRef` so the back-to-TTS button
stays suppressed while progress.location catches up. Skip only when
the user is actively selecting text.
Adds unit tests covering both the cross-section navigation path and
the in-section scrollToAnchor path.
Guard handleTTSSpeak with a single-flight ref so a second tts-speak
event that arrives while the first is still inside its initial awaits
(initMediaSession / backgroundAudio / TTSController.init) is ignored
instead of racing ahead to construct a second TTSController. Without
this, rapid clicks produced two concurrent speakers talking over each
other because viewState.ttsEnabled is only set at the end of the first
invocation, so the footer bar would dispatch tts-speak twice.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a new TTS tab in settings with media metadata update frequency control
(sentence/paragraph/chapter) to reduce Bluetooth notification spam, and move
TTS highlight settings from Color tab to the new TTS tab. Also add a highlight
opacity setting with live preview in the Color tab.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>