Skip to main content

ralph/contracts/config/
notification.rs

1//! Desktop notification configuration.
2//!
3//! Responsibilities:
4//! - Define notification config struct and merge behavior.
5//!
6//! Not handled here:
7//! - Actual notification delivery (see platform-specific notification modules).
8
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11
12/// Desktop notification configuration.
13#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
14#[serde(default, deny_unknown_fields)]
15pub struct NotificationConfig {
16    /// Enable desktop notifications on task completion (default: true).
17    /// This is the legacy/compatibility field; prefer `notify_on_complete`.
18    pub enabled: Option<bool>,
19
20    /// Enable desktop notifications on task completion (default: true).
21    pub notify_on_complete: Option<bool>,
22
23    /// Enable desktop notifications on task failure (default: true).
24    pub notify_on_fail: Option<bool>,
25
26    /// Enable desktop notifications when loop mode completes (default: true).
27    pub notify_on_loop_complete: Option<bool>,
28
29    /// Suppress notifications when a foreground UI client is active (default: true).
30    pub suppress_when_active: Option<bool>,
31
32    /// Enable sound alerts with notifications (default: false).
33    pub sound_enabled: Option<bool>,
34
35    /// Custom sound file path (platform-specific format).
36    /// If not set, uses platform default sounds.
37    pub sound_path: Option<String>,
38
39    /// Notification timeout in milliseconds (default: 8000).
40    #[schemars(range(min = 1000, max = 60000))]
41    pub timeout_ms: Option<u32>,
42}
43
44impl NotificationConfig {
45    pub fn merge_from(&mut self, other: Self) {
46        if other.enabled.is_some() {
47            self.enabled = other.enabled;
48        }
49        if other.notify_on_complete.is_some() {
50            self.notify_on_complete = other.notify_on_complete;
51        }
52        if other.notify_on_fail.is_some() {
53            self.notify_on_fail = other.notify_on_fail;
54        }
55        if other.notify_on_loop_complete.is_some() {
56            self.notify_on_loop_complete = other.notify_on_loop_complete;
57        }
58        if other.suppress_when_active.is_some() {
59            self.suppress_when_active = other.suppress_when_active;
60        }
61        if other.sound_enabled.is_some() {
62            self.sound_enabled = other.sound_enabled;
63        }
64        if other.sound_path.is_some() {
65            self.sound_path = other.sound_path;
66        }
67        if other.timeout_ms.is_some() {
68            self.timeout_ms = other.timeout_ms;
69        }
70    }
71}