feat(reminder): add local TTS for high-strength alarms - #363
Merged
Conversation
Adds on-device text-to-speech for high-strength reminders, replacing the discarded upstream design (which spoke every alarm with sound enabled) with a strength-gated one layered on top of the existing tiered ring sound: only reminder_strength === 'high' triggers speech, via AlarmSoundTier 'full' + a non-empty speech_text. - composeReminderSpeech()/strengthDelivery.ts builds the spoken text as "title, time to go, it's now HH:MM" (or a date-based line for all-day schedules), threaded through AlarmSchedulerPort -> NativeAlarmScheduler -> TimeflowAlarmBridge -> AlarmModule -> AlarmScheduler/AlarmReceiver/AlarmSoundService/RingActivity. - AlarmTtsEngine.kt: a shared TextToSpeech singleton, eagerly bound at AlarmModule construction time (app-launch, almost always foreground) rather than lazily inside AlarmSoundService.onCreate() (often background/restricted) -- reduces the risk of an Android background process restriction blocking the engine bind. - AndroidManifest.xml <queries> entry for android.intent.action.TTS_SERVICE: without it, targetSdk 30+ package visibility hides installed TTS engines from PackageManager and TextToSpeech init fails with status=ERROR even when an engine is genuinely installed and works from system Settings. - Drops upstream's ReminderSpeechFormatter.java/reminderSpeech.ts (the "speak every alarm" formatter) and the now-fully-redundant plugins/withTimeflowAlarm.js, whose permission declarations were already covered by the module's own AndroidManifest.xml. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ertions PR1's presentNow tests (now on main via 1024XEngineer#355) assert 6 native args; this branch's TTS commit added a 7th speech_text param to presentNow's native call. Update the two assertions to match. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Contributor
There was a problem hiding this comment.
已检查固定提交 80b3ec8891d0ad6b16de6d517568b00c5f0d438f...ff98a13f26f495a9da00af1e4fb96988b099c74f 的完整提醒送达链路,重点覆盖时间/地点提醒、强度门控、snooze 持久化、Android Intent 传递与 TTS 失败回退。以下两处会造成用户可见的提醒行为回归。
验证:git diff --check 通过;仓库依赖中的 jest/tsc 可执行文件没有执行权限,因此无法在当前工作区运行聚焦测试或类型检查。
…nous TTS enqueue failure Fixes two P1 findings from fennoai's review of 1024XEngineer#363: - composeReminderSpeech() formatted schedule.start_time unconditionally, but the alarm actually fires at resolveEffectiveTriggerAt(schedule) -- earlier than start_time for before_start reminders, and snoozed_until once snoozed. A 15-minutes-before reminder announced the event's start time instead of the moment it actually rang, and a snoozed reminder kept repeating the original stale time. - AlarmTtsEngine.speak() returns TextToSpeech.ERROR when the engine can't enqueue speech synchronously, but speakCurrent() ignored the return value. No utterance callback fires in that case, so onError() is never reached, and by then the bundled-audio fallback has already been stopped -- the high-strength alarm goes silent. Check the return value and route into the same onSpeechError() fallback used by the async error path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
6ba1979 already removed two of these "定位问题排查完可以删" markers but missed four more with the same marker, still firing unconditionally: - reminderGuardTask.ts: [guard] tick fired on every single guard task wakeup (as often as every 15s in the densest polling tier), and [guard] geofence eval fired on every headless location evaluation. - LocalReminderApplication.ts: the foreground-session equivalents, applyLocationSample and geofence eval, fired on every location callback. Also drops distanceMeters/center, which only existed to feed the deleted logs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
yyy-router
approved these changes
Aug 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
reminder_strength === 'high'时触发(AlarmSoundTier为full且speech_text非空),叠加在原有的三档铃声之上。composeReminderSpeech()/strengthDelivery.ts拼出播报文案("标题,该出发了,现在是 HH:MM",全天日程走按日期的另一套文案),沿AlarmSchedulerPort->NativeAlarmScheduler->TimeflowAlarmBridge->AlarmModule->AlarmScheduler/AlarmReceiver/AlarmSoundService/RingActivity这条链路传下去。AlarmTtsEngine.kt:一个共享的TextToSpeech单例,在AlarmModule构造时(App 启动、几乎总在前台)就主动绑定,不放在AlarmSoundService.onCreate()(常在后台/受限状态)里懒绑定——降低被 Android 后台进程限制卡住绑定的风险。AndroidManifest.xml加了android.intent.action.TTS_SERVICE的<queries>声明:不加的话,targetSdk 30+ 的包可见性限制会让PackageManager看不到已安装的 TTS 引擎,TextToSpeech初始化会报status=ERROR,即便这个引擎在系统设置里确实装着、能用。ReminderSpeechFormatter.java/reminderSpeech.ts,以及因此变得完全冗余的plugins/withTimeflowAlarm.js(它声明的权限已经被模块自己的AndroidManifest.xml覆盖)。AlarmSoundService.java和LocalReminderApplication.ts里残留的 TTS 诊断日志。presentNow()现在会往原生桥多传一个speech_text参数,把 fix(reminder): 三档铃声 + 原生时间触发 + 后台守护补挂 #355 引入的两条presentNow断言同步更新。Test plan
main,npm run check(lint、format、typecheck、jest + vitest,共 703 条测试)本地全部通过。🤖 Generated with Claude Code