From f909516dbf87c59ebb99582d50663b3647864449 Mon Sep 17 00:00:00 2001 From: Nash Aschenbath Date: Sun, 13 Sep 2026 20:15:22 +0800 Subject: [PATCH] feat(notifications): configure in-app notification duration --- README.md | 11 + README.zh-CN.md | 9 + docs/screenshots/notification-duration.png | Bin 0 -> 110524 bytes nebula_app/i18n/en-US.json | 11 +- nebula_app/i18n/zh-CN.json | 11 +- nebula_app/src/gpui_shell/config.rs | 3 + nebula_app/src/gpui_shell/settings_pane.rs | 11 + .../settings_pane/initialization.rs | 11 + .../gpui_shell/settings_pane/localization.rs | 8 + .../gpui_shell/settings_pane/navigation.rs | 2 +- .../gpui_shell/settings_pane/notifications.rs | 36 +++ .../notifications/native_tests.rs | 135 +++++++++ .../settings_pane/notifications/tests.rs | 176 ++++++++++++ .../gpui_shell/settings_pane/setting_help.rs | 5 + .../src/gpui_shell/settings_pane/tests.rs | 1 + nebula_app/src/gpui_shell/toast.rs | 78 +++-- .../src/gpui_shell/toast/duration_tests.rs | 268 ++++++++++++++++++ .../src/gpui_shell/workspace/update_dialog.rs | 8 +- nebula_settings/src/lib.rs | 8 + nebula_settings/src/notifications.rs | 111 ++++++++ nebula_settings/src/reset.rs | 11 + 21 files changed, 879 insertions(+), 35 deletions(-) create mode 100644 docs/screenshots/notification-duration.png create mode 100644 nebula_app/src/gpui_shell/settings_pane/notifications.rs create mode 100644 nebula_app/src/gpui_shell/settings_pane/notifications/native_tests.rs create mode 100644 nebula_app/src/gpui_shell/settings_pane/notifications/tests.rs create mode 100644 nebula_app/src/gpui_shell/toast/duration_tests.rs create mode 100644 nebula_settings/src/notifications.rs diff --git a/README.md b/README.md index fc9cf6b..5ef5221 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,17 @@ The generated Lua configuration uses `require 'pebrel'` and `pebrel.config_builder()`. Invalid reloads retain the last valid configuration. See the [Lua configuration guide](docs/lua-configuration.md) for settings and examples. +In **Settings → Terminal → Alerts**, **Notification duration** selects **Use defaults**, +5, 10, 30, or 90 seconds, or **Until dismissed**. The default preserves existing +lifetimes: short toasts last 5 seconds, message banners 90 seconds, and update +notices remain until dismissed. The saved preference applies to all newly shown +or refreshed in-app cards, independently of the AI-toast visibility switch. +Hiding a card never approves or rejects a request or changes task state; requests +remain available in their terminals and updates in Settings. System notifications +are unchanged. The persisted key is `notification_duration`, with values `default`, +`5`, `10`, `30`, `90`, or `persistent`. +See the [notification duration menu](docs/screenshots/notification-duration.png). + ## Build With the pinned Rust toolchain and the platform dependencies installed: diff --git a/README.zh-CN.md b/README.zh-CN.md index 5a55465..e86524c 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -133,6 +133,15 @@ pebrel config check 生成的 Lua 配置使用 `require 'pebrel'` 与 `pebrel.config_builder()`。 重载失败时继续使用上一份有效配置。设置与示例见 [Lua 配置指南](docs/lua-configuration.md)。 +在 **设置 → 终端 → 提醒** 中,**通知显示时长** 可选择 **保留原有时长**、 +5、10、30、90 秒,或 **保持常驻**。默认保留已有行为:短提示显示 5 秒,消息 +提醒显示 90 秒,更新通知保持常驻。该选项独立于是否显示 AI 提醒的开关,保存后 +对所有新显示或刷新的应用内卡片生效。隐藏卡片不会同意或拒绝请求,也不会改变 +任务状态;请求仍可在对应终端中处理,更新仍可在设置中查看。系统通知不受影响。 +持久化键为 `notification_duration`,可取 `default`、`5`、`10`、`30`、`90` +或 `persistent`。 +可查看[通知显示时长菜单示例](docs/screenshots/notification-duration.png)。 + ## 构建 安装项目固定的 Rust 工具链与对应平台依赖后运行: diff --git a/docs/screenshots/notification-duration.png b/docs/screenshots/notification-duration.png new file mode 100644 index 0000000000000000000000000000000000000000..a1cc76a3aa508f0c3068e077021a4fd00bf14835 GIT binary patch literal 110524 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 flag!(powerline), "ghost" => flag!(ghost), "ai_toasts" => flag!(ai_toasts), + "notification_duration" => pick!(notification_duration), "cjk_bold_regular" => flag!(cjk_bold_regular), "fetch" => flag!(fetch), "keep_session" => flag!(keep_session), @@ -1011,6 +1016,12 @@ impl SettingsPane { self.runtime.ai_toasts, cx, )) + .child(self.select_row( + "notification_duration", + language.text(crate::i18n::Message::SettingsNotificationsDuration), + help("notification_duration", language), + cx, + )) .child(self.select_row( "bell", language.pick("终端铃声", "Terminal bell"), diff --git a/nebula_app/src/gpui_shell/settings_pane/initialization.rs b/nebula_app/src/gpui_shell/settings_pane/initialization.rs index fac590c..107aeca 100644 --- a/nebula_app/src/gpui_shell/settings_pane/initialization.rs +++ b/nebula_app/src/gpui_shell/settings_pane/initialization.rs @@ -32,6 +32,10 @@ impl SettingsPane { if let SelectEvent::Confirm(Some(_)) = event { let row = entity.read(cx).selected_index(cx).map(|path| path.row); if let Some(value) = row.and_then(|row| values.get(row)) { + if key == "notification_duration" { + this.set_notification_duration(value, window, cx); + return; + } this.persist(&[(key, (*value).to_string())], cx); if key == "language" { this.refresh_localized_controls(window, cx); @@ -63,6 +67,13 @@ impl SettingsPane { cx, ); add_select("theme", &THEME_VALUES, runtime.theme.prompt_name(), window, cx); + add_select( + "notification_duration", + nebula_settings::NotificationDuration::VALUES, + runtime.notification_duration.settings_value(), + window, + cx, + ); // 选项顺序与文案照抄旧壳 `CURSOR_SHAPE_OPTIONS` / `cursor_shape_label`。 add_select( "cursor_shape", diff --git a/nebula_app/src/gpui_shell/settings_pane/localization.rs b/nebula_app/src/gpui_shell/settings_pane/localization.rs index 4e0ec1a..2b6e43e 100644 --- a/nebula_app/src/gpui_shell/settings_pane/localization.rs +++ b/nebula_app/src/gpui_shell/settings_pane/localization.rs @@ -22,6 +22,14 @@ pub(super) fn localized_select_labels( language.text(crate::i18n::Message::SettingsQuickTerminalDedicated), language.text(crate::i18n::Message::SettingsQuickTerminalExisting), ], + "notification_duration" => vec![ + language.text(crate::i18n::Message::SettingsNotificationsDurationDefault), + language.text(crate::i18n::Message::SettingsNotificationsDurationFive), + language.text(crate::i18n::Message::SettingsNotificationsDurationTen), + language.text(crate::i18n::Message::SettingsNotificationsDurationThirty), + language.text(crate::i18n::Message::SettingsNotificationsDurationNinety), + language.text(crate::i18n::Message::SettingsNotificationsDurationPersistent), + ], "cursor_shape" => vec![ language.pick("条形(│)", "Bar (│)"), language.pick("下划线(_)", "Underscore (_)"), diff --git a/nebula_app/src/gpui_shell/settings_pane/navigation.rs b/nebula_app/src/gpui_shell/settings_pane/navigation.rs index 7cad2b7..32c51ef 100644 --- a/nebula_app/src/gpui_shell/settings_pane/navigation.rs +++ b/nebula_app/src/gpui_shell/settings_pane/navigation.rs @@ -28,7 +28,7 @@ pub(super) const SECTION_IDS: [&str; 10] = [ pub(super) const SECTION_SEARCH_TERMS: [&str; 10] = [ "application app 应用 update 更新 version 版本 github support 支持", "appearance 外观 theme 主题 font 字体 opacity 透明度 background 背景 cursor 光标 icon 图标", - "profiles 配置文件 shell terminal 终端 completion 补全 startup 启动 ai message notifications toast alerts bell 提醒 通知 弹窗 消息 右下角 ai消息通知 ai 消息通知 ai消息弹窗 ai 消息弹窗 铃声", + "profiles 配置文件 shell terminal 终端 completion 补全 startup 启动 ai message notifications toast alerts bell 提醒 通知 弹窗 消息 右下角 ai消息通知 ai 消息通知 ai消息弹窗 ai 消息弹窗 铃声 duration timeout persistent auto-dismiss 时长 秒 常驻 自动关闭", "providers provider ai 供应商 模型 api", "ssh host 主机 remote 远程 connection 连接", "network 网络 proxy 代理 connectivity 连接", diff --git a/nebula_app/src/gpui_shell/settings_pane/notifications.rs b/nebula_app/src/gpui_shell/settings_pane/notifications.rs new file mode 100644 index 0000000..934a721 --- /dev/null +++ b/nebula_app/src/gpui_shell/settings_pane/notifications.rs @@ -0,0 +1,36 @@ +use super::*; + +#[cfg(all(test, feature = "gpui-test-support"))] +mod tests; + +#[cfg(all(test, feature = "gpui-test-support", target_os = "windows"))] +mod native_tests; + +impl SettingsPane { + pub(super) fn set_notification_duration( + &mut self, + value: &str, + window: &mut Window, + cx: &mut Context, + ) { + if let Err(error) = self.try_persist(&[("notification_duration", value.to_owned())], cx) { + self.sync_select( + "notification_duration", + self.runtime.notification_duration.settings_value(), + window, + cx, + ); + let language = crate::gpui_shell::config::ui_language(cx); + super::super::toast::toast( + window, + cx, + super::super::toast::ToastKind::Warning, + language.format( + crate::i18n::Message::SettingsNotificationsSaveFailed, + &[("error", &error.to_string())], + ), + ); + cx.notify(); + } + } +} diff --git a/nebula_app/src/gpui_shell/settings_pane/notifications/native_tests.rs b/nebula_app/src/gpui_shell/settings_pane/notifications/native_tests.rs new file mode 100644 index 0000000..d197703 --- /dev/null +++ b/nebula_app/src/gpui_shell/settings_pane/notifications/native_tests.rs @@ -0,0 +1,135 @@ +//! Native appearance check using the real settings pane and Select control. +//! Keyboard input goes directly to this test window, never to the desktop. + +use super::*; +use gpui::{Bounds, Keystroke, WindowBounds, WindowOptions, point}; +use std::path::PathBuf; +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +#[test] +#[ignore = "requires a Windows desktop and an isolated PEBREL_NOTIFICATION_QA_DIR/config"] +fn native_notification_duration_menu_keeps_all_choices_visible() { + let output = PathBuf::from(std::env::var_os("PEBREL_NOTIFICATION_QA_DIR").expect("QA output")); + assert!(output.is_absolute()); + assert_eq!( + std::env::var_os("PEBREL_CONFIG_DIR").map(PathBuf::from), + Some(output.join("config")) + ); + let theme = std::env::var("PEBREL_NOTIFICATION_QA_THEME").unwrap_or_else(|_| "Nord".into()); + let theme = ThemeName::from_prompt_name(&theme).expect("built-in theme"); + let chinese = + std::env::var("PEBREL_NOTIFICATION_QA_LANGUAGE").is_ok_and(|value| value == "zh-CN"); + let expected_language = + if chinese { crate::display::UiLanguage::ZhCn } else { crate::display::UiLanguage::EnUs }; + let ready = output.join("ready.json"); + assert!(!ready.exists(), "use a fresh QA output directory"); + let result = Arc::new(Mutex::new(None)); + let after_run = result.clone(); + gpui_platform::application().with_assets(crate::gpui_shell::assets::NebulaAssets).run( + move |cx| { + gpui_component::init(cx); + cx.set_global(crate::gpui_shell::config::Settings::load(theme)); + crate::gpui_shell::theme::apply_chrome_theme(cx); + let mut pane = None; + let handle = cx + .open_window( + WindowOptions { + window_bounds: Some(WindowBounds::Windowed(Bounds::new( + point(px(60.0), px(70.0)), + gpui::size(px(900.0), px(600.0)), + ))), + focus: false, + show: true, + ..Default::default() + }, + |window, cx| { + let view = cx.new(|cx| SettingsPane::new(window, cx)); + view.update(cx, |pane, _| pane.active_section = 2); + pane = Some(view.clone()); + cx.new(|cx| gpui_component::Root::new(view, window, cx)) + }, + ) + .unwrap(); + let pane = pane.unwrap(); + cx.spawn(async move |cx| { + cx.background_executor().timer(Duration::from_millis(500)).await; + let opened = cx + .update_window(handle.into(), |_, window, cx| -> Result<(), String> { + if crate::gpui_shell::config::ui_language(cx) != expected_language + || crate::gpui_shell::theme::effective_theme_name(cx) != theme + || cx.theme().is_dark() + == crate::gpui_shell::theme::chrome_theme(theme).skin().is_light + { + return Err( + "the live theme/language differs from the requested fixture".into(), + ); + } + let view = pane.read(cx); + if view.runtime.notification_duration + != nebula_settings::NotificationDuration::ThirtySeconds + || view.runtime.ai_toasts + { + return Err( + "the duration and delivery switch are not independently loaded" + .into(), + ); + } + let select = view + .select_of("notification_duration") + .ok_or("duration control missing")?; + if select.read(cx).selected_index(cx).map(|path| path.row) != Some(3) { + return Err( + "duration selection does not reflect the persisted value".into() + ); + } + let focus = select.read(cx).focus_handle(cx); + // The Alerts group follows Completion. Scroll the real settings + // viewport before opening its menu; an offscreen anchor is not QA. + window.dispatch_event( + gpui::PlatformInput::ScrollWheel(gpui::ScrollWheelEvent { + position: point(px(500.0), px(400.0)), + delta: gpui::ScrollDelta::Pixels(point(px(0.0), px(-1600.0))), + touch_phase: gpui::TouchPhase::Moved, + modifiers: Default::default(), + }), + cx, + ); + let _ = window.draw(cx); + focus.focus(window, cx); + let _ = window.draw(cx); + window.dispatch_keystroke(Keystroke::parse("enter").unwrap(), cx); + let _ = window.draw(cx); + Ok(()) + }) + .map_err(|error| error.to_string()) + .and_then(|result| result); + if opened.is_ok() { + cx.background_executor().timer(Duration::from_millis(250)).await; + std::fs::write( + &ready, + serde_json::to_vec(&serde_json::json!({ + "pid": std::process::id(), "theme": theme.prompt_name(), + "language": if chinese { "zh-CN" } else { "en-US" }, + "selected_seconds": 30, "ai_toasts_enabled": false, + })) + .unwrap(), + ) + .unwrap(); + for _ in 0..100 { + if output.join("capture-complete").exists() { + break; + } + cx.background_executor().timer(Duration::from_millis(200)).await; + } + } + *result.lock().unwrap() = Some(opened); + drop(pane); + let _ = cx.update_window(handle.into(), |_, window, _| window.remove_window()); + cx.update(|cx| cx.quit()); + }) + .detach(); + }, + ); + assert_eq!(*after_run.lock().unwrap(), Some(Ok(()))); +} diff --git a/nebula_app/src/gpui_shell/settings_pane/notifications/tests.rs b/nebula_app/src/gpui_shell/settings_pane/notifications/tests.rs new file mode 100644 index 0000000..7c3fa63 --- /dev/null +++ b/nebula_app/src/gpui_shell/settings_pane/notifications/tests.rs @@ -0,0 +1,176 @@ +use super::*; +use gpui::{Modifiers, TestAppContext}; + +#[gpui::test] +fn duration_control_is_searchable_keyboard_accessible_and_keeps_the_delivery_switch_independent( + cx: &mut TestAppContext, +) { + cx.update(|cx| { + gpui_component::init(cx); + let mut settings = crate::gpui_shell::config::Settings::load(ThemeName::Nord); + settings.ui_language = crate::display::UiLanguage::EnUs; + settings.ai_toasts = false; + cx.set_global(settings); + }); + let mut pane = None; + let (_, window) = cx.add_window_view(|window, cx| { + let view = cx.new(|cx| SettingsPane::new(window, cx)); + view.update(cx, |pane, cx| { + pane.runtime = + RuntimeSettings::from_raw(&nebula_settings::RawSettings::from_text("ai_toasts=0")); + pane.sync_select("notification_duration", "default", window, cx); + }); + pane = Some(view.clone()); + gpui_component::Root::new(view, window, cx) + }); + let pane = pane.unwrap(); + window.simulate_resize(gpui::size(px(1000.0), px(1100.0))); + window.update(|window, cx| { + pane.update(cx, |pane, cx| { + pane.settings_search_input + .update(cx, |input, cx| input.replace_all("自动关闭", window, cx)); + }) + }); + window.run_until_parked(); + window.update(|window, cx| { + let _ = window.draw(cx); + }); + assert_eq!(pane.read_with(window, |pane, _| pane.active_section), 2); + let bounds = window.debug_bounds("notification-duration-select").expect("duration select"); + assert_eq!(bounds.size.width, px(SETTINGS_SELECT_WIDTH)); + window.simulate_click(bounds.center(), Modifiers::default()); + window.run_until_parked(); + window.simulate_keystrokes("escape"); + window.run_until_parked(); + for key in ["enter", "escape"] { + window.simulate_keystrokes(key); + window.run_until_parked(); + } + pane.read_with(window, |pane, cx| { + assert!(!pane.runtime.ai_toasts, "duration must remain available while reminders are off"); + let select = pane.select_of("notification_duration").unwrap(); + assert_eq!(select.read(cx).selected_index(cx).unwrap().row, 0); + assert_eq!(pane.setting_override("notification_duration"), Some((false, "default".into()))); + }); +} + +#[test] +fn duration_options_have_localized_labels_and_stable_persisted_values() { + for language in [crate::display::UiLanguage::EnUs, crate::display::UiLanguage::ZhCn] { + let labels = localized_select_labels( + "notification_duration", + nebula_settings::NotificationDuration::VALUES, + language, + ); + assert_eq!(labels.len(), 6); + assert_eq!( + labels.first().unwrap().as_ref(), + language.text(crate::i18n::Message::SettingsNotificationsDurationDefault) + ); + assert_eq!( + labels.last().unwrap().as_ref(), + language.text(crate::i18n::Message::SettingsNotificationsDurationPersistent) + ); + } +} + +/// This test writes settings, so it is opt-in and refuses a nonempty fixture. +/// Run it alone with both QA/config variables pointing to the same fresh directory. +#[gpui::test] +#[ignore = "requires a fresh, isolated PEBREL_NOTIFICATION_QA_DIR and matching PEBREL_CONFIG_DIR"] +fn choosing_a_duration_persists_it_and_a_failed_save_restores_the_visible_selection( + cx: &mut TestAppContext, +) { + let directory = std::path::PathBuf::from( + std::env::var_os("PEBREL_NOTIFICATION_QA_DIR").expect("isolated QA directory"), + ); + assert!(directory.is_absolute()); + assert_eq!(Some(directory.as_os_str()), std::env::var_os("PEBREL_CONFIG_DIR").as_deref()); + std::fs::create_dir_all(&directory).unwrap(); + let path = directory.join("pebrel_settings.txt"); + assert!(!path.exists(), "never overwrite an existing configuration"); + std::fs::write(&path, "ai_toasts=0\nlanguage=en-US\ncustom_data=keep\n").unwrap(); + cx.update(|cx| { + gpui_component::init(cx); + cx.set_global(crate::gpui_shell::config::Settings::load(ThemeName::Nord)); + }); + let mut pane = None; + let (_, window) = cx.add_window_view(|window, cx| { + let view = cx.new(|cx| SettingsPane::new(window, cx)); + view.update(cx, |pane, _| pane.active_section = 2); + pane = Some(view.clone()); + gpui_component::Root::new(view, window, cx) + }); + let pane = pane.unwrap(); + window.simulate_resize(gpui::size(px(1000.0), px(1100.0))); + window.run_until_parked(); + window.update(|window, cx| { + let _ = window.draw(cx); + }); + let bounds = window.debug_bounds("notification-duration-select").expect("duration select"); + window.simulate_click(bounds.center(), Modifiers::default()); + window.run_until_parked(); + for key in ["down", "down", "down", "down", "down", "enter"] { + window.simulate_keystrokes(key); + window.run_until_parked(); + window.update(|window, cx| { + let _ = window.draw(cx); + }); + } + assert_eq!( + RuntimeSettings::load().notification_duration, + nebula_settings::NotificationDuration::Persistent + ); + assert!(!RuntimeSettings::load().ai_toasts); + window.update(|_, cx| { + assert_eq!( + cx.global::().notification_duration, + nebula_settings::NotificationDuration::Persistent + ); + }); + assert!(std::fs::read_to_string(&path).unwrap().contains("custom_data=keep")); + + // A directory in place of the settings file deterministically rejects a save, + // without changing ACLs or touching the user's real configuration. + let backup = directory.join("settings-before-error.txt"); + std::fs::rename(&path, &backup).unwrap(); + std::fs::create_dir(&path).unwrap(); + let bounds = window.debug_bounds("notification-duration-select").unwrap(); + window.simulate_click(bounds.center(), Modifiers::default()); + window.run_until_parked(); + for key in ["up", "up", "up", "up", "up", "enter"] { + window.simulate_keystrokes(key); + window.run_until_parked(); + window.update(|window, cx| { + let _ = window.draw(cx); + }); + } + pane.read_with(window, |pane, cx| { + assert_eq!( + pane.runtime.notification_duration, + nebula_settings::NotificationDuration::Persistent + ); + assert_eq!( + pane.select_of("notification_duration") + .unwrap() + .read(cx) + .selected_index(cx) + .unwrap() + .row, + 5 + ); + }); + window.update(|window, cx| { + let root = window.root::().flatten().unwrap(); + assert!( + !root.read(cx).notification.read(cx).notifications().is_empty(), + "save failure needs visible feedback" + ); + }); + std::fs::remove_dir(&path).unwrap(); + std::fs::rename(&backup, &path).unwrap(); + assert_eq!( + RuntimeSettings::load().notification_duration, + nebula_settings::NotificationDuration::Persistent + ); +} diff --git a/nebula_app/src/gpui_shell/settings_pane/setting_help.rs b/nebula_app/src/gpui_shell/settings_pane/setting_help.rs index c73d6ba..2024cff 100644 --- a/nebula_app/src/gpui_shell/settings_pane/setting_help.rs +++ b/nebula_app/src/gpui_shell/settings_pane/setting_help.rs @@ -91,6 +91,10 @@ pub(super) fn help(key: &str, language: UiLanguage) -> SettingHelp { language.text(crate::i18n::Message::SettingsNotificationsAiMessagesDescription), Some(language.text(crate::i18n::Message::SettingsNotificationsAiMessagesDetails)), ), + "notification_duration" => ( + language.text(crate::i18n::Message::SettingsNotificationsDurationDescription), + Some(language.text(crate::i18n::Message::SettingsNotificationsDurationDetails)), + ), "font_family" => ( language.pick("按字体组顺序查找可用字形。", "Uses the font group in order to find available glyphs."), Some(language.pick("前面的字体缺少字形时,会继续尝试后面的字体。", "When a font lacks a glyph, the next font in the group is tried.")), @@ -188,6 +192,7 @@ mod tests { "startup_directory", "bell", "ai_toasts", + "notification_duration", "font_family", "ghost", "accept", diff --git a/nebula_app/src/gpui_shell/settings_pane/tests.rs b/nebula_app/src/gpui_shell/settings_pane/tests.rs index f2f663a..b89b649 100644 --- a/nebula_app/src/gpui_shell/settings_pane/tests.rs +++ b/nebula_app/src/gpui_shell/settings_pane/tests.rs @@ -96,6 +96,7 @@ fn localized_select_labels_keep_stable_value_cardinality() { ("cursor_shape", &["beam", "underline", "block", "hollow"]), ("tabs_position", &["sidebar", "top"]), ("bell", &["off", "visual", "sound", "both"]), + ("notification_duration", nebula_settings::NotificationDuration::VALUES), ]; for (key, values) in cases { for language in crate::display::UiLanguage::ALL { diff --git a/nebula_app/src/gpui_shell/toast.rs b/nebula_app/src/gpui_shell/toast.rs index 3a05e2f..c7db874 100644 --- a/nebula_app/src/gpui_shell/toast.rs +++ b/nebula_app/src/gpui_shell/toast.rs @@ -1,18 +1,20 @@ //! GPUI 壳的提示三层(裁定见 `display/toast.rs` 模块文档): //! //! - **toast**:已经结束的事实,没有待办动作(恢复成功、复制完成)。 -//! 走组件库 `NotificationList`(`Root` 自带渲染层),5s 自动消失。 +//! 走组件库 `NotificationList`(`Root` 自带渲染层),默认 5s 自动消失。 //! - **消息栏**:值得驻留、要用户看见的事(配置错误、断路器、AI 回合完成)。 -//! 同一渲染层,停留 [`BANNER_TTL`] 后自动退场,带关闭按钮。**驻留不等于 -//! 永不消失**——理由见那个常量的文档。 +//! 同一渲染层,默认停留 [`BANNER_TTL`] 后自动退场,带关闭按钮。 //! - **模态**:阻断性决策。调用点自建 `Dialog`,不经此模块。 //! +//! 通知显示时长可覆盖 toast、消息栏及更新卡片的默认值,也可保持常驻; +//! 仅影响新显示或刷新的卡片,不改变系统通知、实际待处理状态或确认结果。 +//! //! 判据是「有没有待办动作」而不是严重度。toast 允许自动消失的前提:每条 //! 同时落一行日志——toast 不承载唯一副本,错过那几秒的用户仍能在日志里 //! 查到。 //! -//! 与旧壳合同的已知差异(组件库行为):停留 5s(旧壳 4.2s)、无同屏三条 -//! 上限(5s 自动退场,溢出概率低);重复冷却在本层补齐(600ms,同文本)。 +//! 与旧壳合同的已知差异:默认停留 5s(旧壳 4.2s);同屏保留最新三条, +//! 重复冷却在本层补齐(600ms,同文本)。 use std::sync::Mutex; use std::sync::atomic::{AtomicU64, Ordering}; @@ -29,6 +31,7 @@ pub use crate::display::ToastKind; /// 同一文本短时间连发只刷新语境,不叠一摞相同卡片(旧壳 /// `DUPLICATE_COOLDOWN` 同值同义;只挡逐字相同的文本)。 const DUPLICATE_COOLDOWN: Duration = Duration::from_millis(600); +const TOAST_TTL: Duration = Duration::from_secs(5); /// 组件库 Notification 默认固定 `w_112`,短短一句也会铺成近半个窗口。 /// Nebula 的 toast 按内容收缩;过长消息到此上限后自然换行。 @@ -53,7 +56,11 @@ static NEXT_AI_TOAST: AtomicU64 = AtomicU64::new(1); /// A component notification identity, not a separate queue or lifetime manager. struct AiToast; -/// Apply preference changes to existing in-app notices in every open window. +#[cfg(all(test, feature = "gpui-test-support"))] +mod duration_tests; + +/// Apply the AI visibility switch to existing notices in every open window. +/// Duration changes apply at insertion or refresh, not to already visible cards. /// Native system notifications have a separate delivery path and remain intact. pub(crate) fn init(cx: &mut App) { cx.observe_global::(|cx| { @@ -122,18 +129,15 @@ fn note(kind: ToastKind, text: String) -> Notification { /// 到当前 effect cycle 结束,正常运行期仍同步推送。 /// 允许少数业务通知复用 Nebula 的启动期 Root 守卫和右下角宿主;通知内容与 /// 动作仍由业务模块定义,避免把更新等领域状态塞进通用 toast 层。 -pub(crate) fn push_notification(window: &mut Window, cx: &mut App, notification: Notification) { - if window.root::().flatten().is_some() { - window.push_notification(notification, cx); - } else { - window.defer(cx, move |window, cx| { - if window.root::().flatten().is_some() { - window.push_notification(notification, cx); - } else { - log::warn!("notification dropped because the window Root is unavailable"); - } - }); - } +/// `default_timeout` preserves the caller's original lifetime in default mode; +/// `None` means the card originally remained until dismissed. +pub(crate) fn push_notification( + window: &mut Window, + cx: &mut App, + notification: Notification, + default_timeout: Option, +) { + push_with_duration(window, cx, notification, default_timeout, false); } /// Nebula 的通知层定位:右下角固定 20px 安全边距,最新一条在最下,旧的 @@ -170,17 +174,17 @@ pub fn render_layer(window: &mut Window, cx: &mut App) -> Option { ) } -/// 弹一条自动消失的提示(toast 层)。空文本与 600ms 内的重复文本静默丢弃。 +/// 弹一条短提示(默认自动消失)。空文本与 600ms 内的重复文本静默丢弃。 pub fn toast(window: &mut Window, cx: &mut App, kind: ToastKind, text: impl Into) { let text = text.into(); if text.trim().is_empty() || is_duplicate(&text) { return; } log::info!("toast [{kind:?}]: {text}"); - push_notification(window, cx, note(kind, text)); + push_notification(window, cx, note(kind, text), Some(TOAST_TTL)); } -/// 驻留一条消息(消息栏层):停留远长于 toast,但**有上限**,见 +/// 驻留一条消息(消息栏层):默认停留远长于 toast,但**有上限**,见 /// [`BANNER_TTL`]。用于「值得驻留」的事——没什么值得驻留的请用 [`toast`]。 /// /// 这里也挂一个空的 `on_click`:组件库的点击包装会先 `dismiss` 再调回调, @@ -317,13 +321,23 @@ fn reply_to_confirmation( } fn push_banner(window: &mut Window, cx: &mut App, notification: Notification, ai_toast: bool) { + push_with_duration(window, cx, notification, Some(BANNER_TTL), ai_toast); +} + +fn push_with_duration( + window: &mut Window, + cx: &mut App, + notification: Notification, + default_timeout: Option, + ai_toast: bool, +) { if ai_toast && !super::config::ai_toasts_enabled(cx) { return; } let note = notification.autohide(false); if window.root::().flatten().is_some() { window.push_notification(note, cx); - schedule_banner_dismiss(window, cx); + schedule_notification_dismiss(window, cx, default_timeout); } else { window.defer(cx, move |window, cx| { // The preference may change before the startup Root is installed. @@ -332,28 +346,38 @@ fn push_banner(window: &mut Window, cx: &mut App, notification: Notification, ai } if window.root::().flatten().is_some() { window.push_notification(note, cx); - schedule_banner_dismiss(window, cx); + schedule_notification_dismiss(window, cx, default_timeout); } else { - log::warn!("banner dropped because the window Root is unavailable"); + log::warn!("notification dropped because the window Root is unavailable"); } }); } } -/// 给刚推入的驻留消息挂一个到点自尽的定时器。 +/// 给刚推入的通知挂一个到期关闭的定时器;常驻模式不创建定时器。 /// /// 组件库把 autohide 做成 bool、5s 写死在 `NotificationList::push` 里,没有 /// 自定义时长的入口;为这个改 fork 要动 rev pin,不值得。换个办法:push 之后 /// 队尾那条就是我们刚推的,抓住它的**弱**引用,到点自己调 `dismiss`。用户 /// 提前点掉时弱引用已经失效,定时器什么也不做,不会去误关后来的消息。 -fn schedule_banner_dismiss(window: &mut Window, cx: &mut App) { +fn schedule_notification_dismiss( + window: &mut Window, + cx: &mut App, + default_timeout: Option, +) { + let timeout = cx + .try_global::() + .map(|settings| settings.notification_duration) + .unwrap_or_default() + .timeout(default_timeout); + let Some(timeout) = timeout else { return }; let Some(Some(root)) = window.root::() else { return }; let list = root.read(cx).notification.clone(); let Some(note) = list.read(cx).notifications().last().cloned() else { return }; let note = note.downgrade(); window .spawn(cx, async move |cx| { - cx.background_executor().timer(BANNER_TTL).await; + cx.background_executor().timer(timeout).await; let _ = cx.update(|window, cx| { if let Some(note) = note.upgrade() { note.update(cx, |note, cx| note.dismiss(window, cx)); diff --git a/nebula_app/src/gpui_shell/toast/duration_tests.rs b/nebula_app/src/gpui_shell/toast/duration_tests.rs new file mode 100644 index 0000000..7de3ee8 --- /dev/null +++ b/nebula_app/src/gpui_shell/toast/duration_tests.rs @@ -0,0 +1,268 @@ +use super::*; +use gpui::{ + AppContext as _, BorrowAppContext as _, Context, EntityId, Render, TestAppContext, + VisualTestContext, +}; +use nebula_settings::NotificationDuration; +use std::{cell::Cell, rc::Rc}; + +use crate::gpui_shell::config::Settings; +use crate::gpui_shell::terminal::confirmation::BinaryConfirmation; + +struct Empty; + +impl Render for Empty { + fn render(&mut self, _: &mut Window, _: &mut Context) -> impl gpui::IntoElement { + div() + } +} + +fn open(duration: NotificationDuration, cx: &mut TestAppContext) -> VisualTestContext { + cx.update(|cx| { + gpui_component::init(cx); + let mut settings = Settings::load(nebula_settings::ThemeName::Nord); + settings.ai_toasts = true; + settings.notification_duration = duration; + cx.set_global(settings); + init(cx); + }); + let (_, window) = cx.add_window_view(|window, cx| Root::new(cx.new(|_| Empty), window, cx)); + window.clone() +} + +fn ids(cx: &mut VisualTestContext) -> Vec { + cx.update(|window, cx| window.notifications(cx).iter().map(|note| note.entity_id()).collect()) +} + +fn advance(cx: &mut VisualTestContext, duration: Duration) { + cx.run_until_parked(); + cx.background_executor.advance_clock(duration); + cx.run_until_parked(); +} + +fn settle_dismissal(cx: &mut VisualTestContext) { + advance(cx, Duration::from_millis(250)); +} + +#[gpui::test] +fn every_timed_mode_expires_at_its_selected_duration_without_activating_actions( + cx: &mut TestAppContext, +) { + for duration in [ + NotificationDuration::FiveSeconds, + NotificationDuration::TenSeconds, + NotificationDuration::ThirtySeconds, + NotificationDuration::NinetySeconds, + ] { + let mut window = open(duration, cx); + let actions = Rc::new(Cell::new(0)); + let invoked = actions.clone(); + let update_action = actions.clone(); + window.update(|window, cx| { + toast(window, cx, ToastKind::Success, format!("Timed toast {duration:?}")); + banner(window, cx, ToastKind::Warning, "Timed non-AI banner"); + push_notification( + window, + cx, + note(ToastKind::Info, "Update-style action fixture".into()) + .on_click(move |_, _, _| update_action.set(update_action.get() + 1)), + None, + ); + push_banner( + window, + cx, + note(ToastKind::Info, "Timed fixture".into()) + .id::() + .on_click(move |_, _, _| invoked.set(invoked.get() + 1)), + true, + ); + }); + advance(&mut window, duration.timeout(None).unwrap() - Duration::from_millis(1)); + assert_eq!(ids(&mut window).len(), 4, "every card kind uses the selected lifetime"); + advance(&mut window, Duration::from_millis(1)); + settle_dismissal(&mut window); + assert!(ids(&mut window).is_empty()); + assert_eq!(actions.get(), 0, "autohide must not perform the card's action"); + } +} + +#[gpui::test] +fn persistent_mode_has_no_expiry_and_retains_manual_dismissal(cx: &mut TestAppContext) { + let mut window = open(NotificationDuration::Persistent, cx); + window.update(|window, cx| { + toast(window, cx, ToastKind::Success, "Persistent ordinary toast"); + banner(window, cx, ToastKind::Warning, "Persistent non-AI banner"); + push_notification( + window, + cx, + note(ToastKind::Info, "Persistent update fixture".into()), + None, + ); + confirmation_for_pane( + window, + cx, + "Persistent confirmation".into(), + 8201, + BinaryConfirmation { id: 8301, question: "Continue?".into() }, + ); + }); + advance(&mut window, Duration::from_secs(3600)); + assert_eq!(ids(&mut window).len(), 4); + window.update(|window, cx| { + for notification in window.notifications(cx).iter() { + notification.update(cx, |notification, cx| notification.dismiss(window, cx)); + } + }); + settle_dismissal(&mut window); + assert!(ids(&mut window).is_empty()); +} + +#[gpui::test] +fn refreshed_confirmation_gets_a_new_deadline_even_if_the_old_entity_is_retained( + cx: &mut TestAppContext, +) { + let mut window = open(NotificationDuration::FiveSeconds, cx); + let push = |window: &mut Window, cx: &mut App| { + confirmation_for_pane( + window, + cx, + "Refreshed confirmation".into(), + 8202, + BinaryConfirmation { id: 8302, question: "Continue?".into() }, + ); + }; + let old = window.update(|window, cx| { + push(window, cx); + window.notifications(cx)[0].clone() + }); + advance(&mut window, Duration::from_secs(3)); + window.update(push); + let refreshed = ids(&mut window); + assert_eq!(refreshed.len(), 1); + assert_ne!(refreshed[0], old.entity_id()); + advance(&mut window, Duration::from_secs(2)); + settle_dismissal(&mut window); + assert_eq!(ids(&mut window), refreshed, "the original deadline cannot close the replacement"); + advance(&mut window, Duration::from_secs(3)); + settle_dismissal(&mut window); + assert!(ids(&mut window).is_empty()); +} + +#[gpui::test] +fn a_refreshed_persistent_card_cannot_be_closed_by_its_predecessors_timer(cx: &mut TestAppContext) { + let mut window = open(NotificationDuration::FiveSeconds, cx); + let push = |window: &mut Window, cx: &mut App| { + confirmation_for_pane( + window, + cx, + "Mode switch fixture".into(), + 8203, + BinaryConfirmation { id: 8303, question: "Continue?".into() }, + ); + }; + window.update(push); + advance(&mut window, Duration::from_secs(3)); + window.update(|window, cx| { + cx.update_global::(|settings, _| { + settings.notification_duration = NotificationDuration::Persistent + }); + push(window, cx); + }); + let refreshed = ids(&mut window); + advance(&mut window, Duration::from_secs(3600)); + assert_eq!(ids(&mut window), refreshed); +} + +#[gpui::test] +fn default_mode_preserves_short_toasts_banners_and_persistent_update_notices( + cx: &mut TestAppContext, +) { + let mut window = open(NotificationDuration::Default, cx); + let persistent = window.update(|window, cx| { + push_notification(window, cx, note(ToastKind::Info, "Default update fixture".into()), None); + let persistent = window.notifications(cx)[0].entity_id(); + banner(window, cx, ToastKind::Warning, "Default non-AI configuration fixture"); + toast(window, cx, ToastKind::Success, "Default ordinary copy fixture"); + confirmation_for_pane( + window, + cx, + "Default confirmation fixture".into(), + 8204, + BinaryConfirmation { id: 8304, question: "Continue?".into() }, + ); + persistent + }); + assert_eq!(ids(&mut window).len(), 4); + advance(&mut window, Duration::from_secs(5)); + settle_dismissal(&mut window); + assert_eq!(ids(&mut window).len(), 3); + advance(&mut window, Duration::from_secs(85)); + settle_dismissal(&mut window); + assert_eq!(ids(&mut window), vec![persistent]); + advance(&mut window, Duration::from_secs(3600)); + assert_eq!(ids(&mut window), vec![persistent]); +} + +#[gpui::test] +fn duration_still_applies_to_non_ai_cards_when_ai_visibility_is_off(cx: &mut TestAppContext) { + let mut window = open(NotificationDuration::TenSeconds, cx); + window.update(|window, cx| { + cx.update_global::(|settings, _| settings.ai_toasts = false); + toast(window, cx, ToastKind::Success, "Independent duration toast"); + banner(window, cx, ToastKind::Warning, "Independent duration banner"); + push_notification( + window, + cx, + note(ToastKind::Info, "Independent update fixture".into()), + None, + ); + confirmation_for_pane( + window, + cx, + "Hidden AI fixture".into(), + 8205, + BinaryConfirmation { id: 8305, question: "Continue?".into() }, + ); + }); + assert_eq!(ids(&mut window).len(), 3); + advance(&mut window, Duration::from_secs(10)); + settle_dismissal(&mut window); + assert!(ids(&mut window).is_empty()); +} + +#[gpui::test] +fn a_duration_change_does_not_reschedule_already_visible_cards(cx: &mut TestAppContext) { + let mut window = open(NotificationDuration::FiveSeconds, cx); + window.update(|window, cx| banner(window, cx, ToastKind::Info, "Already visible fixture")); + advance(&mut window, Duration::from_secs(2)); + let persistent = window.update(|window, cx| { + cx.update_global::(|settings, _| { + settings.notification_duration = NotificationDuration::Persistent + }); + banner(window, cx, ToastKind::Info, "New persistent fixture"); + window.notifications(cx).last().unwrap().entity_id() + }); + advance(&mut window, Duration::from_secs(3)); + settle_dismissal(&mut window); + assert_eq!(ids(&mut window), vec![persistent]); + advance(&mut window, Duration::from_secs(3600)); + assert_eq!(ids(&mut window), vec![persistent]); +} + +#[gpui::test] +fn startup_notifications_use_the_latest_duration_after_the_root_is_installed( + cx: &mut TestAppContext, +) { + let _ = open(NotificationDuration::FiveSeconds, cx); + let (_, window) = cx.add_window_view(|window, cx| { + toast(window, cx, ToastKind::Success, "Deferred startup duration fixture"); + banner(window, cx, ToastKind::Warning, "Deferred startup banner fixture"); + cx.update_global::(|settings, _| { + settings.notification_duration = NotificationDuration::Persistent + }); + Root::new(cx.new(|_| Empty), window, cx) + }); + let mut window = window.clone(); + advance(&mut window, Duration::from_secs(3600)); + assert_eq!(ids(&mut window).len(), 2); +} diff --git a/nebula_app/src/gpui_shell/workspace/update_dialog.rs b/nebula_app/src/gpui_shell/workspace/update_dialog.rs index b5353e5..b2188a1 100644 --- a/nebula_app/src/gpui_shell/workspace/update_dialog.rs +++ b/nebula_app/src/gpui_shell/workspace/update_dialog.rs @@ -12,7 +12,7 @@ const UPDATE_DIALOG_STATUS_HEIGHT: f32 = 280.0; struct UpdateNotification; -/// 自动检查只在右下角提示,不抢终端焦点;更新是待办,因此保持到用户处理。 +/// 自动检查只在右下角提示,不抢终端焦点;默认常驻,也遵循通知时长设置。 pub(crate) fn show_update_notification( result: crate::update_check::UpdateCheckResult, window: &mut Window, @@ -31,7 +31,6 @@ pub(crate) fn show_update_notification( let notification = Notification::warning(message) .id::() .title(title) - .autohide(false) .w_auto() .min_w(px(300.0)) .max_w(px(440.0)) @@ -49,7 +48,7 @@ pub(crate) fn show_update_notification( result.latest, result.current ); - crate::gpui_shell::toast::push_notification(window, cx, notification); + crate::gpui_shell::toast::push_notification(window, cx, notification, None); } fn start_update_download( @@ -148,7 +147,6 @@ fn show_download_outcome_notification( notification = notification .id::() .title(title) - .autohide(false) .w_auto() .min_w(px(320.0)) .max_w(px(460.0)) @@ -161,7 +159,7 @@ fn show_download_outcome_notification( }), ) }); - crate::gpui_shell::toast::push_notification(window, cx, notification); + crate::gpui_shell::toast::push_notification(window, cx, notification, None); } /// 关闭、Esc 与遮罩点击均走“3 天后提醒”;这正是新 Dialog 的取消合同。 diff --git a/nebula_settings/src/lib.rs b/nebula_settings/src/lib.rs index e097f80..365cf2a 100644 --- a/nebula_settings/src/lib.rs +++ b/nebula_settings/src/lib.rs @@ -18,6 +18,8 @@ use std::collections::HashMap; mod app_icon; pub use app_icon::{AppIconName, AppIconPalette}; mod language; +mod notifications; +pub use notifications::NotificationDuration; mod quick_terminal; mod themes; pub use language::{LanguageInfo, LanguagePref}; @@ -978,6 +980,8 @@ pub struct RuntimeSettings { /// AI message toasts inside the application. System notifications and /// terminal/tab state are independent. Default on for existing users. pub ai_toasts: bool, + /// Display lifetime for in-app cards; default mode retains each kind's lifetime. + pub notification_duration: NotificationDuration, /// 新会话欢迎屏 fastfetch(默认关:启动速度优先于观感,旧壳裁定)。 pub fetch: bool, /// Check GitHub Releases after startup. Manual checks remain available @@ -1125,6 +1129,10 @@ impl RuntimeSettings { .unwrap_or_default(), bell: raw.value("bell").and_then(BellModeName::from_settings).unwrap_or_default(), ai_toasts: raw.bool_on("ai_toasts").unwrap_or(true), + notification_duration: raw + .value("notification_duration") + .and_then(NotificationDuration::from_settings) + .unwrap_or_default(), fetch: raw.bool_on("fetch").unwrap_or(false), auto_check_updates: raw.bool_on("auto_check_updates").unwrap_or(true), keep_session: raw.bool_on("keep_session").unwrap_or(false), diff --git a/nebula_settings/src/notifications.rs b/nebula_settings/src/notifications.rs new file mode 100644 index 0000000..17589db --- /dev/null +++ b/nebula_settings/src/notifications.rs @@ -0,0 +1,111 @@ +//! Shared in-app notification lifetime, independent of delivery and approvals. + +use std::time::Duration; + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum NotificationDuration { + #[default] + Default, + FiveSeconds, + TenSeconds, + ThirtySeconds, + NinetySeconds, + Persistent, +} + +impl NotificationDuration { + pub const VALUES: &'static [&'static str] = &["default", "5", "10", "30", "90", "persistent"]; + + pub fn from_settings(value: &str) -> Option { + match value.trim() { + value if value.eq_ignore_ascii_case("default") => Some(Self::Default), + "5" => Some(Self::FiveSeconds), + "10" => Some(Self::TenSeconds), + "30" => Some(Self::ThirtySeconds), + "90" => Some(Self::NinetySeconds), + value if value.eq_ignore_ascii_case("persistent") => Some(Self::Persistent), + _ => None, + } + } + + pub fn settings_value(self) -> &'static str { + match self { + Self::Default => "default", + Self::FiveSeconds => "5", + Self::TenSeconds => "10", + Self::ThirtySeconds => "30", + Self::NinetySeconds => "90", + Self::Persistent => "persistent", + } + } + + /// Retain each caller's previous lifetime unless the user overrides it. + /// `None` is an originally persistent notification, such as an update action. + pub fn timeout(self, default: Option) -> Option { + match self { + Self::Default => default, + Self::FiveSeconds => Some(Duration::from_secs(5)), + Self::TenSeconds => Some(Duration::from_secs(10)), + Self::ThirtySeconds => Some(Duration::from_secs(30)), + Self::NinetySeconds => Some(Duration::from_secs(90)), + Self::Persistent => None, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{RawSettings, RuntimeSettings, apply_updates}; + + #[test] + fn missing_or_invalid_duration_preserves_each_notifications_existing_lifetime() { + assert_eq!( + RuntimeSettings::from_raw(&RawSettings::from_text("")).notification_duration, + NotificationDuration::Default + ); + for value in ["", "invalid", "-1", "0", "1", "999999999999999999999"] { + let settings = RuntimeSettings::from_raw(&RawSettings::from_text(&format!( + "notification_duration={value}\n" + ))); + assert_eq!(settings.notification_duration, NotificationDuration::Default); + for default in [Some(Duration::from_secs(5)), Some(Duration::from_secs(90)), None] { + assert_eq!(settings.notification_duration.timeout(default), default); + } + } + } + + #[test] + fn every_duration_round_trips_without_changing_the_delivery_switch_or_other_data() { + for value in NotificationDuration::VALUES { + let saved = apply_updates( + "ai_toasts=0\ncustom_data=keep\n", + &[("notification_duration", (*value).to_owned())], + ); + let settings = RuntimeSettings::from_raw(&RawSettings::from_text(&saved)); + assert_eq!(settings.notification_duration.settings_value(), *value); + assert!(!settings.ai_toasts); + assert!(saved.contains("custom_data=keep")); + } + } + + #[test] + fn timed_and_persistent_modes_have_explicit_lifetimes() { + for (value, seconds) in [("5", 5), ("10", 10), ("30", 30), ("90", 90)] { + assert_eq!( + NotificationDuration::from_settings(value).unwrap().timeout(None), + Some(Duration::from_secs(seconds)) + ); + } + assert_eq!( + NotificationDuration::from_settings(" PERSISTENT ") + .unwrap() + .timeout(Some(Duration::from_secs(5))), + None + ); + assert_eq!( + NotificationDuration::from_settings(" DEFAULT "), + Some(NotificationDuration::Default) + ); + } +} diff --git a/nebula_settings/src/reset.rs b/nebula_settings/src/reset.rs index 2e8c87c..f2ef28c 100644 --- a/nebula_settings/src/reset.rs +++ b/nebula_settings/src/reset.rs @@ -34,6 +34,7 @@ const RESET_KEYS: &[&str] = &[ "vcs_display", "bell", "ai_toasts", + "notification_duration", "fetch", "auto_check_updates", "keep_session", @@ -125,6 +126,16 @@ mod tests { assert!(RuntimeSettings::from_raw(&RawSettings::from_text(&restored)).ai_toasts); } + #[test] + fn resetting_preferences_restores_the_default_notification_duration() { + let original = "notification_duration=persistent\ncustom_data=keep\n"; + let restored = default_settings_text(original); + let runtime = RuntimeSettings::from_raw(&RawSettings::from_text(&restored)); + assert_eq!(runtime.notification_duration, crate::NotificationDuration::Default); + assert!(restored.contains("custom_data=keep")); + assert!(!restored.contains("notification_duration=")); + } + #[test] fn reset_removes_all_overrides_and_keeps_user_data() { let text = "# preferences\r\n THEME = Nord\r\ncopy_on_select=1\nkeybind=ctrl+x:Copy\nFONT_SIZE=30\nexecutor=custom\nblur=acrylic\nopacity=0.65\nssh_hosts=saved-host\nai_provider=custom\nfuture_setting=keep\n";