feat(tui): allow custom completion sound files#2512
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces support for playing a custom completion sound file on Windows when a turn completes in the TUI, adding a new completion_sound = "file" option and a sound_file configuration path. The reviewer feedback focuses on preventing potential log spam by ensuring warnings for unsupported platforms or missing sound files are only logged once per session. Additionally, the reviewer recommends explicitly documenting that only WAV files are supported by the underlying Windows PlaySoundW API in both the codebase doc comments and the configuration documentation.
| #[cfg(not(target_os = "windows"))] | ||
| fn play_sound_file(_path: &Path) { | ||
| tracing::warn!("completion_sound = \"file\" is currently supported on Windows only"); | ||
| } |
There was a problem hiding this comment.
On non-Windows platforms, if completion_sound = "file" is configured, this warning will be logged on every single turn completion, leading to unnecessary log spam. Consider using a static AtomicBool to log this warning only once per session.
#[cfg(not(target_os = "windows"))]
fn play_sound_file(_path: &Path) {
static WARNED: AtomicBool = AtomicBool::new(false);
if !WARNED.swap(true, Ordering::Relaxed) {
tracing::warn!("completion_sound = \"file\" is currently supported on Windows only");
}
}| fn file_sound() { | ||
| if let Some(path) = configured_sound_file() { | ||
| play_sound_file(&path); | ||
| } else { | ||
| tracing::warn!("completion_sound = \"file\" requires [notifications].sound_file"); | ||
| } | ||
| } |
There was a problem hiding this comment.
If completion_sound = "file" is configured but sound_file is missing, this warning will be logged on every single turn completion. Consider using a static AtomicBool to log this warning only once per session to prevent log spam.
fn file_sound() {
if let Some(path) = configured_sound_file() {
play_sound_file(&path);
} else {
static WARNED: AtomicBool = AtomicBool::new(false);
if !WARNED.swap(true, Ordering::Relaxed) {
tracing::warn!("completion_sound = \"file\" requires [notifications].sound_file");
}
}
}| /// Path to the sound file used when `completion_sound = "file"`. | ||
| #[serde(default)] | ||
| pub sound_file: Option<PathBuf>, |
There was a problem hiding this comment.
The Windows PlaySoundW API only supports Waveform Audio (WAV) files. It is highly recommended to specify this format restriction in the doc comments so users do not attempt to configure unsupported formats like MP3 or OGG.
/// Path to the WAV sound file used when completion_sound = "file".
#[serde(default)]
pub sound_file: Option<PathBuf>,| - `[notifications].sound_file` (path, optional): path to a custom sound file | ||
| used when `completion_sound = "file"`. |
There was a problem hiding this comment.
The Windows PlaySoundW API only supports Waveform Audio (WAV) files. Explicitly documenting this format restriction helps prevent users from trying to use unsupported formats like MP3 or OGG.
| - `[notifications].sound_file` (path, optional): path to a custom sound file | |
| used when `completion_sound = "file"`. | |
| - [notifications].sound_file (path, optional): path to a custom WAV sound file | |
| used when completion_sound = "file". |
| #[test] | ||
| fn settings_installs_custom_completion_sound_file() { | ||
| let config: crate::config::Config = toml::from_str( | ||
| r#" | ||
| [notifications] | ||
| completion_sound = "file" | ||
| sound_file = "E:\\google\\downloads\\xm4114.wav" | ||
| "#, | ||
| ) | ||
| .expect("custom completion sound config should parse"); | ||
|
|
||
| let _ = settings(&config); | ||
|
|
||
| let (mode, file) = completion_sound_state_for_tests(); | ||
| assert_eq!(mode, crate::config::CompletionSound::File); | ||
| assert_eq!( | ||
| file.as_deref(), | ||
| Some(std::path::Path::new("E:\\google\\downloads\\xm4114.wav")) | ||
| ); | ||
| } |
There was a problem hiding this comment.
Test reads shared global state without serialization
settings_installs_custom_completion_sound_file writes COMPLETION_SOUND_MODE and COMPLETION_SOUND_FILE (both process-wide statics) via settings(), then immediately reads them back via completion_sound_state_for_tests(). Tests in crates/tui/src/tui/ui/tests.rs (e.g. notification_settings_tui_always_keeps_configured_method_no_threshold) also call crate::tui::notifications::settings() concurrently, which resets the same statics to Beep/None. Under the default parallel test runner this can produce a spurious assertion failure on the File or path check. The pre-existing env-sensitive tests already use env_lock() for similar reasons; adding the same mutex here (or annotating with #[serial]) would prevent this race.
Refs #2484
Problem
completion_soundsupports the built-in modes, but Windows users cannot point CodeWhale at a custom notification sound file from config.Change
completion_sound = "file"and[notifications].sound_fileVerification
cargo test -p codewhale-tui custom_completion_sound --all-features --locked -- --nocapturecargo test -p codewhale-tui tui::notifications::tests --all-features --locked -- --nocapturecargo fmt --all -- --checkcargo check -p codewhale-tui --all-features --lockedcargo clippy -p codewhale-tui --all-features --locked -- -D warningsgit diff --check origin/main..HEADGreptile Summary
This PR adds
completion_sound = "file"as a new option that lets Windows users specify a custom WAV file path ([notifications].sound_file) that is played asynchronously viaPlaySoundWat turn completion.CompletionSound::Filevariant, thesound_file: Option<PathBuf>config field, and Windows-onlyplay_sound_filebacked byPlaySoundW(SND_FILENAME | SND_ASYNC | SND_NODEFAULT).AtomicBoolflags to limit log noise when the feature is misconfigured (missing path, or used on a non-Windows platform), along with corresponding unit tests and documentation updates.Confidence Score: 5/5
Safe to merge; the change is additive and self-contained, with no impact on existing sound modes or notification delivery paths.
The Windows PlaySoundW path is correctly wired with SND_FILENAME | SND_ASYNC | SND_NODEFAULT, the once-fire warning guards prevent log spam, and the new config field degrades gracefully on unsupported platforms. The only notable concern is that the warn-once flags are never reset after the user repairs and then re-breaks the configuration, which could silently suppress a useful diagnostic but does not affect correctness or stability.
crates/tui/src/tui/notifications.rs — specifically the interaction between set_completion_sound and the COMPLETION_SOUND_FILE_MISSING_WARNED flag across config reloads.
Important Files Changed
Reviews (2): Last reviewed commit: "fix(tui): clarify custom completion soun..." | Re-trigger Greptile