@delorenj/claude-notifications 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/DO.md +5 -0
  2. package/FIXES-APPLIED.md +195 -0
  3. package/INTEGRATION.md +445 -0
  4. package/LAYOUT-INTEGRATION.md +191 -0
  5. package/QUICK-REFERENCE.md +195 -0
  6. package/README.md +145 -14
  7. package/TASK.md +15 -0
  8. package/ZELLIJ-NOTIFY.md +523 -0
  9. package/_bmad-output/implementation-artifacts/spec-install-multi-cli-hooks.md +241 -0
  10. package/bin/claude-notifications.js +417 -312
  11. package/bin/claude-notify.js +47 -1
  12. package/bin/zellij-notify.js +346 -0
  13. package/bun.lock +35 -0
  14. package/diagnose-zellij.sh +105 -0
  15. package/examples/settings-with-zellij.json +18 -0
  16. package/examples/settings-zellij-only.json +18 -0
  17. package/examples/zellij-notify-examples.sh +143 -0
  18. package/lib/adapters/_stub.js +35 -0
  19. package/lib/adapters/auggie.js +10 -0
  20. package/lib/adapters/claude-code.js +181 -0
  21. package/lib/adapters/codex.js +10 -0
  22. package/lib/adapters/copilot.js +10 -0
  23. package/lib/adapters/gemini.js +10 -0
  24. package/lib/adapters/index.js +240 -0
  25. package/lib/adapters/kimi.js +10 -0
  26. package/lib/adapters/opencode.js +14 -0
  27. package/lib/adapters/vibe.js +10 -0
  28. package/lib/config.js +44 -8
  29. package/lib/tui.js +115 -0
  30. package/lib/zellij.js +248 -0
  31. package/package.json +6 -4
  32. package/postinstall.js +28 -25
  33. package/preuninstall.js +18 -9
  34. package/test/adapters/claude-code.test.js +144 -0
  35. package/test/adapters/patches.test.js +81 -0
  36. package/test/adapters/registry.test.js +89 -0
  37. package/test/adapters/stubs.test.js +46 -0
  38. package/test/cli-json.test.js +79 -0
  39. package/test/helpers/fake-fs.js +59 -0
  40. package/test-integration.sh +113 -0
  41. package/test-notification-plugin.kdl +34 -0
  42. package/test-updated-layout.sh +75 -0
  43. package/test-zellij-cli.sh +72 -0
  44. package/zellij-plugin/.cargo/config.toml +5 -0
  45. package/zellij-plugin/.github/workflows/ci.yml +97 -0
  46. package/zellij-plugin/Cargo.lock +3558 -0
  47. package/zellij-plugin/Cargo.toml +40 -0
  48. package/zellij-plugin/README.md +290 -0
  49. package/zellij-plugin/build.sh +179 -0
  50. package/zellij-plugin/configs/examples/accessibility.kdl +31 -0
  51. package/zellij-plugin/configs/examples/catppuccin.kdl +32 -0
  52. package/zellij-plugin/configs/examples/default.kdl +34 -0
  53. package/zellij-plugin/configs/examples/minimal.kdl +22 -0
  54. package/zellij-plugin/docs/CONFIGURATION.md +191 -0
  55. package/zellij-plugin/docs/INTEGRATION.md +333 -0
  56. package/zellij-plugin/src/animation.rs +451 -0
  57. package/zellij-plugin/src/colors.rs +407 -0
  58. package/zellij-plugin/src/config.rs +664 -0
  59. package/zellij-plugin/src/event_bridge.rs +339 -0
  60. package/zellij-plugin/src/main.rs +420 -0
  61. package/zellij-plugin/src/notification.rs +466 -0
  62. package/zellij-plugin/src/queue.rs +399 -0
  63. package/zellij-plugin/src/renderer.rs +477 -0
  64. package/zellij-plugin/src/state.rs +338 -0
  65. package/zellij-plugin/src/tests.rs +413 -0
  66. package/.claude/checkpoints/1756392335.json +0 -1
  67. package/.claude/checkpoints/1756392341.json +0 -1
  68. package/.claude/checkpoints/1756392347.json +0 -1
  69. package/.claude/checkpoints/1756392376.json +0 -1
  70. package/.claude/checkpoints/1756392377.json +0 -1
  71. package/.claude/checkpoints/1756392386.json +0 -1
  72. package/.claude/checkpoints/1756392387.json +0 -1
  73. package/.claude/checkpoints/1756392398.json +0 -1
  74. package/.claude/checkpoints/1756392400.json +0 -1
  75. package/.claude/checkpoints/1756392427.json +0 -1
  76. package/.claude/checkpoints/1756392428.json +0 -1
  77. package/.claude/checkpoints/1756392486.json +0 -1
  78. package/.claude/checkpoints/1756392488.json +0 -1
  79. package/.claude/checkpoints/1756392558.json +0 -1
  80. package/.claude/checkpoints/1756392559.json +0 -1
  81. package/.claude/checkpoints/summary-session-20250828-105040.md +0 -57
  82. package/.claude/checkpoints/task-1756392207.json +0 -1
  83. package/.claude/checkpoints/task-1756392742.json +0 -1
  84. package/.claude/commands/analysis/COMMAND_COMPLIANCE_REPORT.md +0 -54
  85. package/.claude/commands/analysis/README.md +0 -9
  86. package/.claude/commands/analysis/bottleneck-detect.md +0 -162
  87. package/.claude/commands/analysis/performance-bottlenecks.md +0 -59
  88. package/.claude/commands/analysis/performance-report.md +0 -25
  89. package/.claude/commands/analysis/token-efficiency.md +0 -45
  90. package/.claude/commands/analysis/token-usage.md +0 -25
  91. package/.claude/commands/automation/README.md +0 -9
  92. package/.claude/commands/automation/auto-agent.md +0 -122
  93. package/.claude/commands/automation/self-healing.md +0 -106
  94. package/.claude/commands/automation/session-memory.md +0 -90
  95. package/.claude/commands/automation/smart-agents.md +0 -73
  96. package/.claude/commands/automation/smart-spawn.md +0 -25
  97. package/.claude/commands/automation/workflow-select.md +0 -25
  98. package/.claude/commands/coordination/README.md +0 -9
  99. package/.claude/commands/coordination/agent-spawn.md +0 -25
  100. package/.claude/commands/coordination/init.md +0 -44
  101. package/.claude/commands/coordination/orchestrate.md +0 -43
  102. package/.claude/commands/coordination/spawn.md +0 -45
  103. package/.claude/commands/coordination/swarm-init.md +0 -85
  104. package/.claude/commands/coordination/task-orchestrate.md +0 -25
  105. package/.claude/commands/github/README.md +0 -11
  106. package/.claude/commands/github/code-review-swarm.md +0 -514
  107. package/.claude/commands/github/code-review.md +0 -25
  108. package/.claude/commands/github/github-modes.md +0 -147
  109. package/.claude/commands/github/github-swarm.md +0 -121
  110. package/.claude/commands/github/issue-tracker.md +0 -292
  111. package/.claude/commands/github/issue-triage.md +0 -25
  112. package/.claude/commands/github/multi-repo-swarm.md +0 -519
  113. package/.claude/commands/github/pr-enhance.md +0 -26
  114. package/.claude/commands/github/pr-manager.md +0 -170
  115. package/.claude/commands/github/project-board-sync.md +0 -471
  116. package/.claude/commands/github/release-manager.md +0 -338
  117. package/.claude/commands/github/release-swarm.md +0 -544
  118. package/.claude/commands/github/repo-analyze.md +0 -25
  119. package/.claude/commands/github/repo-architect.md +0 -367
  120. package/.claude/commands/github/swarm-issue.md +0 -482
  121. package/.claude/commands/github/swarm-pr.md +0 -285
  122. package/.claude/commands/github/sync-coordinator.md +0 -301
  123. package/.claude/commands/github/workflow-automation.md +0 -442
  124. package/.claude/commands/hooks/README.md +0 -11
  125. package/.claude/commands/hooks/overview.md +0 -58
  126. package/.claude/commands/hooks/post-edit.md +0 -117
  127. package/.claude/commands/hooks/post-task.md +0 -112
  128. package/.claude/commands/hooks/pre-edit.md +0 -113
  129. package/.claude/commands/hooks/pre-task.md +0 -111
  130. package/.claude/commands/hooks/session-end.md +0 -118
  131. package/.claude/commands/hooks/setup.md +0 -103
  132. package/.claude/commands/memory/README.md +0 -9
  133. package/.claude/commands/memory/memory-persist.md +0 -25
  134. package/.claude/commands/memory/memory-search.md +0 -25
  135. package/.claude/commands/memory/memory-usage.md +0 -25
  136. package/.claude/commands/memory/neural.md +0 -47
  137. package/.claude/commands/memory/usage.md +0 -46
  138. package/.claude/commands/monitoring/README.md +0 -9
  139. package/.claude/commands/monitoring/agent-metrics.md +0 -25
  140. package/.claude/commands/monitoring/agents.md +0 -44
  141. package/.claude/commands/monitoring/real-time-view.md +0 -25
  142. package/.claude/commands/monitoring/status.md +0 -46
  143. package/.claude/commands/monitoring/swarm-monitor.md +0 -25
  144. package/.claude/commands/optimization/README.md +0 -9
  145. package/.claude/commands/optimization/auto-topology.md +0 -62
  146. package/.claude/commands/optimization/cache-manage.md +0 -25
  147. package/.claude/commands/optimization/parallel-execute.md +0 -25
  148. package/.claude/commands/optimization/parallel-execution.md +0 -50
  149. package/.claude/commands/optimization/topology-optimize.md +0 -25
  150. package/.claude/commands/pair/README.md +0 -261
  151. package/.claude/commands/pair/commands.md +0 -546
  152. package/.claude/commands/pair/config.md +0 -510
  153. package/.claude/commands/pair/examples.md +0 -512
  154. package/.claude/commands/pair/modes.md +0 -348
  155. package/.claude/commands/pair/session.md +0 -407
  156. package/.claude/commands/pair/start.md +0 -209
  157. package/.claude/commands/sparc/analyzer.md +0 -52
  158. package/.claude/commands/sparc/architect.md +0 -53
  159. package/.claude/commands/sparc/batch-executor.md +0 -54
  160. package/.claude/commands/sparc/coder.md +0 -54
  161. package/.claude/commands/sparc/debugger.md +0 -54
  162. package/.claude/commands/sparc/designer.md +0 -53
  163. package/.claude/commands/sparc/documenter.md +0 -54
  164. package/.claude/commands/sparc/innovator.md +0 -54
  165. package/.claude/commands/sparc/memory-manager.md +0 -54
  166. package/.claude/commands/sparc/optimizer.md +0 -54
  167. package/.claude/commands/sparc/orchestrator.md +0 -132
  168. package/.claude/commands/sparc/researcher.md +0 -54
  169. package/.claude/commands/sparc/reviewer.md +0 -54
  170. package/.claude/commands/sparc/sparc-modes.md +0 -174
  171. package/.claude/commands/sparc/swarm-coordinator.md +0 -54
  172. package/.claude/commands/sparc/tdd.md +0 -54
  173. package/.claude/commands/sparc/tester.md +0 -54
  174. package/.claude/commands/sparc/workflow-manager.md +0 -54
  175. package/.claude/commands/stream-chain/pipeline.md +0 -121
  176. package/.claude/commands/stream-chain/run.md +0 -70
  177. package/.claude/commands/swarm/analysis.md +0 -95
  178. package/.claude/commands/swarm/development.md +0 -96
  179. package/.claude/commands/swarm/examples.md +0 -168
  180. package/.claude/commands/swarm/maintenance.md +0 -102
  181. package/.claude/commands/swarm/optimization.md +0 -117
  182. package/.claude/commands/swarm/research.md +0 -136
  183. package/.claude/commands/swarm/testing.md +0 -131
  184. package/.claude/commands/training/README.md +0 -9
  185. package/.claude/commands/training/model-update.md +0 -25
  186. package/.claude/commands/training/neural-patterns.md +0 -74
  187. package/.claude/commands/training/neural-train.md +0 -25
  188. package/.claude/commands/training/pattern-learn.md +0 -25
  189. package/.claude/commands/training/specialization.md +0 -63
  190. package/.claude/commands/truth/start.md +0 -143
  191. package/.claude/commands/verify/check.md +0 -50
  192. package/.claude/commands/verify/start.md +0 -128
  193. package/.claude/commands/workflows/README.md +0 -9
  194. package/.claude/commands/workflows/development.md +0 -78
  195. package/.claude/commands/workflows/research.md +0 -63
  196. package/.claude/commands/workflows/workflow-create.md +0 -25
  197. package/.claude/commands/workflows/workflow-execute.md +0 -25
  198. package/.claude/commands/workflows/workflow-export.md +0 -25
  199. package/.claude/config.json +0 -36
  200. package/.claude/settings.json +0 -162
  201. package/.claude-flow/metrics/agent-metrics.json +0 -1
  202. package/.claude-flow/metrics/performance.json +0 -9
  203. package/.claude-flow/metrics/system-metrics.json +0 -230
  204. package/.claude-flow/metrics/task-metrics.json +0 -10
  205. package/FIXES.md +0 -75
  206. package/test-results.md +0 -163
@@ -0,0 +1,466 @@
1
+ //! Notification module for Zellij Visual Notifications
2
+ //!
3
+ //! Defines notification types, structures, and processing logic.
4
+
5
+ use serde::{Deserialize, Serialize};
6
+
7
+ /// Notification type enumeration
8
+ #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9
+ pub enum NotificationType {
10
+ /// Command completed successfully (exit code 0)
11
+ Success,
12
+ /// Command failed (non-zero exit code)
13
+ Error,
14
+ /// Warning notification
15
+ Warning,
16
+ /// Informational notification
17
+ Info,
18
+ /// Progress update
19
+ Progress,
20
+ /// Attention needed (Claude Code waiting)
21
+ Attention,
22
+ }
23
+
24
+ impl Default for NotificationType {
25
+ fn default() -> Self {
26
+ Self::Info
27
+ }
28
+ }
29
+
30
+ impl NotificationType {
31
+ /// Get the icon for this notification type
32
+ pub fn icon(&self) -> Option<String> {
33
+ Some(match self {
34
+ NotificationType::Success => "\u{2714}".to_string(), // Check mark
35
+ NotificationType::Error => "\u{2718}".to_string(), // X mark
36
+ NotificationType::Warning => "\u{26A0}".to_string(), // Warning triangle
37
+ NotificationType::Info => "\u{2139}".to_string(), // Info symbol
38
+ NotificationType::Progress => "\u{21BB}".to_string(), // Rotating arrow
39
+ NotificationType::Attention => "\u{2757}".to_string(), // Exclamation mark
40
+ })
41
+ }
42
+
43
+ /// Get the display name for this notification type
44
+ pub fn name(&self) -> &'static str {
45
+ match self {
46
+ NotificationType::Success => "success",
47
+ NotificationType::Error => "error",
48
+ NotificationType::Warning => "warning",
49
+ NotificationType::Info => "info",
50
+ NotificationType::Progress => "progress",
51
+ NotificationType::Attention => "attention",
52
+ }
53
+ }
54
+
55
+ /// Get urgency level (0 = low, 1 = normal, 2 = high, 3 = critical)
56
+ pub fn urgency(&self) -> u8 {
57
+ match self {
58
+ NotificationType::Info => 0,
59
+ NotificationType::Progress => 0,
60
+ NotificationType::Success => 1,
61
+ NotificationType::Warning => 2,
62
+ NotificationType::Error => 3,
63
+ NotificationType::Attention => 3,
64
+ }
65
+ }
66
+
67
+ /// Parse notification type from string
68
+ pub fn from_str(s: &str) -> Self {
69
+ match s.to_lowercase().as_str() {
70
+ "success" | "ok" | "done" | "complete" | "completed" => NotificationType::Success,
71
+ "error" | "fail" | "failed" | "failure" => NotificationType::Error,
72
+ "warning" | "warn" => NotificationType::Warning,
73
+ "info" | "information" => NotificationType::Info,
74
+ "progress" | "running" | "working" => NotificationType::Progress,
75
+ "attention" | "waiting" | "input" | "input_needed" => NotificationType::Attention,
76
+ _ => NotificationType::Info,
77
+ }
78
+ }
79
+
80
+ /// Check if this notification type should use urgent animation
81
+ pub fn is_urgent(&self) -> bool {
82
+ matches!(self, NotificationType::Error | NotificationType::Attention)
83
+ }
84
+ }
85
+
86
+ /// Priority level for notifications
87
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
88
+ pub enum Priority {
89
+ /// Low priority (queued, can be delayed)
90
+ Low = 0,
91
+ /// Normal priority (standard processing)
92
+ Normal = 1,
93
+ /// High priority (processed before normal)
94
+ High = 2,
95
+ /// Critical priority (processed immediately)
96
+ Critical = 3,
97
+ }
98
+
99
+ impl Default for Priority {
100
+ fn default() -> Self {
101
+ Self::Normal
102
+ }
103
+ }
104
+
105
+ impl From<&NotificationType> for Priority {
106
+ fn from(notification_type: &NotificationType) -> Self {
107
+ match notification_type {
108
+ NotificationType::Info => Priority::Low,
109
+ NotificationType::Progress => Priority::Low,
110
+ NotificationType::Success => Priority::Normal,
111
+ NotificationType::Warning => Priority::High,
112
+ NotificationType::Error => Priority::Critical,
113
+ NotificationType::Attention => Priority::Critical,
114
+ }
115
+ }
116
+ }
117
+
118
+ /// Notification structure
119
+ #[derive(Debug, Clone, Serialize, Deserialize)]
120
+ pub struct Notification {
121
+ /// Unique notification ID
122
+ pub id: String,
123
+ /// Notification type
124
+ pub notification_type: NotificationType,
125
+ /// Notification message
126
+ pub message: String,
127
+ /// Title (optional)
128
+ pub title: Option<String>,
129
+ /// Target pane ID (if specific to a pane)
130
+ pub pane_id: Option<u32>,
131
+ /// Target tab index (if specific to a tab)
132
+ pub tab_index: Option<usize>,
133
+ /// Priority level
134
+ pub priority: Priority,
135
+ /// Timestamp when notification was created (Unix timestamp ms)
136
+ pub timestamp: u64,
137
+ /// Time-to-live in milliseconds (0 = no expiry)
138
+ pub ttl_ms: u64,
139
+ /// Source of the notification
140
+ pub source: String,
141
+ /// Additional metadata
142
+ pub metadata: NotificationMetadata,
143
+ }
144
+
145
+ impl Default for Notification {
146
+ fn default() -> Self {
147
+ Self {
148
+ id: generate_id(),
149
+ notification_type: NotificationType::Info,
150
+ message: String::new(),
151
+ title: None,
152
+ pane_id: None,
153
+ tab_index: None,
154
+ priority: Priority::Normal,
155
+ timestamp: 0,
156
+ ttl_ms: 300_000, // 5 minutes default
157
+ source: "unknown".to_string(),
158
+ metadata: NotificationMetadata::default(),
159
+ }
160
+ }
161
+ }
162
+
163
+ impl Notification {
164
+ /// Create a new notification
165
+ pub fn new(notification_type: NotificationType, message: &str) -> Self {
166
+ let priority = Priority::from(&notification_type);
167
+ Self {
168
+ id: generate_id(),
169
+ notification_type,
170
+ message: message.to_string(),
171
+ priority,
172
+ ..Default::default()
173
+ }
174
+ }
175
+
176
+ /// Create a success notification
177
+ pub fn success(message: &str) -> Self {
178
+ Self::new(NotificationType::Success, message)
179
+ }
180
+
181
+ /// Create an error notification
182
+ pub fn error(message: &str) -> Self {
183
+ Self::new(NotificationType::Error, message)
184
+ }
185
+
186
+ /// Create a warning notification
187
+ pub fn warning(message: &str) -> Self {
188
+ Self::new(NotificationType::Warning, message)
189
+ }
190
+
191
+ /// Create an info notification
192
+ pub fn info(message: &str) -> Self {
193
+ Self::new(NotificationType::Info, message)
194
+ }
195
+
196
+ /// Create an attention notification (Claude Code waiting)
197
+ pub fn attention(message: &str) -> Self {
198
+ Self::new(NotificationType::Attention, message)
199
+ }
200
+
201
+ /// Create a progress notification
202
+ pub fn progress(message: &str) -> Self {
203
+ Self::new(NotificationType::Progress, message)
204
+ }
205
+
206
+ /// Set the title
207
+ pub fn with_title(mut self, title: &str) -> Self {
208
+ self.title = Some(title.to_string());
209
+ self
210
+ }
211
+
212
+ /// Set the target pane
213
+ pub fn for_pane(mut self, pane_id: u32) -> Self {
214
+ self.pane_id = Some(pane_id);
215
+ self
216
+ }
217
+
218
+ /// Set the target tab
219
+ pub fn for_tab(mut self, tab_index: usize) -> Self {
220
+ self.tab_index = Some(tab_index);
221
+ self
222
+ }
223
+
224
+ /// Set the source
225
+ pub fn from_source(mut self, source: &str) -> Self {
226
+ self.source = source.to_string();
227
+ self
228
+ }
229
+
230
+ /// Set the TTL
231
+ pub fn with_ttl(mut self, ttl_ms: u64) -> Self {
232
+ self.ttl_ms = ttl_ms;
233
+ self
234
+ }
235
+
236
+ /// Set the timestamp
237
+ pub fn at_time(mut self, timestamp: u64) -> Self {
238
+ self.timestamp = timestamp;
239
+ self
240
+ }
241
+
242
+ /// Set the priority
243
+ pub fn with_priority(mut self, priority: Priority) -> Self {
244
+ self.priority = priority;
245
+ self
246
+ }
247
+
248
+ /// Check if the notification has expired
249
+ pub fn is_expired(&self, current_time: u64) -> bool {
250
+ if self.ttl_ms == 0 {
251
+ return false;
252
+ }
253
+ current_time > self.timestamp + self.ttl_ms
254
+ }
255
+
256
+ /// Get the notification icon
257
+ pub fn icon(&self) -> Option<String> {
258
+ self.notification_type.icon()
259
+ }
260
+
261
+ /// Get display text (title + message or just message)
262
+ pub fn display_text(&self) -> String {
263
+ if let Some(ref title) = self.title {
264
+ format!("{}: {}", title, self.message)
265
+ } else {
266
+ self.message.clone()
267
+ }
268
+ }
269
+ }
270
+
271
+ /// Additional metadata for notifications
272
+ #[derive(Debug, Clone, Default, Serialize, Deserialize)]
273
+ pub struct NotificationMetadata {
274
+ /// Command that triggered the notification
275
+ pub command: Option<String>,
276
+ /// Exit code (for command completion)
277
+ pub exit_code: Option<i32>,
278
+ /// Duration in milliseconds
279
+ pub duration_ms: Option<u64>,
280
+ /// Additional custom data
281
+ pub custom: Option<serde_json::Value>,
282
+ }
283
+
284
+ /// Generate a unique notification ID
285
+ fn generate_id() -> String {
286
+ use std::time::{SystemTime, UNIX_EPOCH};
287
+
288
+ let duration = SystemTime::now()
289
+ .duration_since(UNIX_EPOCH)
290
+ .unwrap_or_default();
291
+
292
+ format!("notif-{}-{}", duration.as_millis(), rand_u32())
293
+ }
294
+
295
+ /// Simple pseudo-random number generator (WASM compatible)
296
+ fn rand_u32() -> u32 {
297
+ use std::time::{SystemTime, UNIX_EPOCH};
298
+
299
+ let time = SystemTime::now()
300
+ .duration_since(UNIX_EPOCH)
301
+ .unwrap_or_default();
302
+
303
+ // Simple LCG-based PRNG
304
+ let seed = time.as_nanos() as u32;
305
+ seed.wrapping_mul(1103515245).wrapping_add(12345)
306
+ }
307
+
308
+ /// Builder for creating notifications
309
+ pub struct NotificationBuilder {
310
+ notification: Notification,
311
+ }
312
+
313
+ impl NotificationBuilder {
314
+ /// Create a new notification builder
315
+ pub fn new() -> Self {
316
+ Self {
317
+ notification: Notification::default(),
318
+ }
319
+ }
320
+
321
+ /// Set the notification type
322
+ pub fn notification_type(mut self, t: NotificationType) -> Self {
323
+ self.notification.notification_type = t.clone();
324
+ self.notification.priority = Priority::from(&t);
325
+ self
326
+ }
327
+
328
+ /// Set the message
329
+ pub fn message(mut self, msg: &str) -> Self {
330
+ self.notification.message = msg.to_string();
331
+ self
332
+ }
333
+
334
+ /// Set the title
335
+ pub fn title(mut self, title: &str) -> Self {
336
+ self.notification.title = Some(title.to_string());
337
+ self
338
+ }
339
+
340
+ /// Set the pane ID
341
+ pub fn pane_id(mut self, id: u32) -> Self {
342
+ self.notification.pane_id = Some(id);
343
+ self
344
+ }
345
+
346
+ /// Set the tab index
347
+ pub fn tab_index(mut self, index: usize) -> Self {
348
+ self.notification.tab_index = Some(index);
349
+ self
350
+ }
351
+
352
+ /// Set the source
353
+ pub fn source(mut self, source: &str) -> Self {
354
+ self.notification.source = source.to_string();
355
+ self
356
+ }
357
+
358
+ /// Set the TTL
359
+ pub fn ttl(mut self, ttl_ms: u64) -> Self {
360
+ self.notification.ttl_ms = ttl_ms;
361
+ self
362
+ }
363
+
364
+ /// Set the timestamp
365
+ pub fn timestamp(mut self, ts: u64) -> Self {
366
+ self.notification.timestamp = ts;
367
+ self
368
+ }
369
+
370
+ /// Set the priority
371
+ pub fn priority(mut self, p: Priority) -> Self {
372
+ self.notification.priority = p;
373
+ self
374
+ }
375
+
376
+ /// Set command metadata
377
+ pub fn command(mut self, cmd: &str) -> Self {
378
+ self.notification.metadata.command = Some(cmd.to_string());
379
+ self
380
+ }
381
+
382
+ /// Set exit code metadata
383
+ pub fn exit_code(mut self, code: i32) -> Self {
384
+ self.notification.metadata.exit_code = Some(code);
385
+ self
386
+ }
387
+
388
+ /// Set duration metadata
389
+ pub fn duration(mut self, duration_ms: u64) -> Self {
390
+ self.notification.metadata.duration_ms = Some(duration_ms);
391
+ self
392
+ }
393
+
394
+ /// Build the notification
395
+ pub fn build(self) -> Notification {
396
+ self.notification
397
+ }
398
+ }
399
+
400
+ impl Default for NotificationBuilder {
401
+ fn default() -> Self {
402
+ Self::new()
403
+ }
404
+ }
405
+
406
+ #[cfg(test)]
407
+ mod tests {
408
+ use super::*;
409
+
410
+ #[test]
411
+ fn test_notification_creation() {
412
+ let notif = Notification::success("Build completed");
413
+ assert_eq!(notif.notification_type, NotificationType::Success);
414
+ assert_eq!(notif.message, "Build completed");
415
+ }
416
+
417
+ #[test]
418
+ fn test_notification_builder() {
419
+ let notif = NotificationBuilder::new()
420
+ .notification_type(NotificationType::Error)
421
+ .message("Test failed")
422
+ .pane_id(42)
423
+ .command("npm test")
424
+ .exit_code(1)
425
+ .build();
426
+
427
+ assert_eq!(notif.notification_type, NotificationType::Error);
428
+ assert_eq!(notif.pane_id, Some(42));
429
+ assert_eq!(notif.metadata.command, Some("npm test".to_string()));
430
+ assert_eq!(notif.metadata.exit_code, Some(1));
431
+ }
432
+
433
+ #[test]
434
+ fn test_notification_type_icons() {
435
+ assert!(NotificationType::Success.icon().is_some());
436
+ assert!(NotificationType::Error.icon().is_some());
437
+ assert!(NotificationType::Warning.icon().is_some());
438
+ }
439
+
440
+ #[test]
441
+ fn test_notification_type_parsing() {
442
+ assert_eq!(NotificationType::from_str("success"), NotificationType::Success);
443
+ assert_eq!(NotificationType::from_str("ERROR"), NotificationType::Error);
444
+ assert_eq!(NotificationType::from_str("warn"), NotificationType::Warning);
445
+ assert_eq!(NotificationType::from_str("attention"), NotificationType::Attention);
446
+ assert_eq!(NotificationType::from_str("unknown"), NotificationType::Info);
447
+ }
448
+
449
+ #[test]
450
+ fn test_notification_expiry() {
451
+ let notif = Notification::new(NotificationType::Info, "Test")
452
+ .at_time(1000)
453
+ .with_ttl(5000);
454
+
455
+ assert!(!notif.is_expired(5000));
456
+ assert!(notif.is_expired(7000));
457
+ }
458
+
459
+ #[test]
460
+ fn test_priority_from_type() {
461
+ assert_eq!(Priority::from(&NotificationType::Info), Priority::Low);
462
+ assert_eq!(Priority::from(&NotificationType::Success), Priority::Normal);
463
+ assert_eq!(Priority::from(&NotificationType::Warning), Priority::High);
464
+ assert_eq!(Priority::from(&NotificationType::Error), Priority::Critical);
465
+ }
466
+ }