@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.
- package/DO.md +5 -0
- package/FIXES-APPLIED.md +195 -0
- package/INTEGRATION.md +445 -0
- package/LAYOUT-INTEGRATION.md +191 -0
- package/QUICK-REFERENCE.md +195 -0
- package/README.md +145 -14
- package/TASK.md +15 -0
- package/ZELLIJ-NOTIFY.md +523 -0
- package/_bmad-output/implementation-artifacts/spec-install-multi-cli-hooks.md +241 -0
- package/bin/claude-notifications.js +417 -312
- package/bin/claude-notify.js +47 -1
- package/bin/zellij-notify.js +346 -0
- package/bun.lock +35 -0
- package/diagnose-zellij.sh +105 -0
- package/examples/settings-with-zellij.json +18 -0
- package/examples/settings-zellij-only.json +18 -0
- package/examples/zellij-notify-examples.sh +143 -0
- package/lib/adapters/_stub.js +35 -0
- package/lib/adapters/auggie.js +10 -0
- package/lib/adapters/claude-code.js +181 -0
- package/lib/adapters/codex.js +10 -0
- package/lib/adapters/copilot.js +10 -0
- package/lib/adapters/gemini.js +10 -0
- package/lib/adapters/index.js +240 -0
- package/lib/adapters/kimi.js +10 -0
- package/lib/adapters/opencode.js +14 -0
- package/lib/adapters/vibe.js +10 -0
- package/lib/config.js +44 -8
- package/lib/tui.js +115 -0
- package/lib/zellij.js +248 -0
- package/package.json +6 -4
- package/postinstall.js +28 -25
- package/preuninstall.js +18 -9
- package/test/adapters/claude-code.test.js +144 -0
- package/test/adapters/patches.test.js +81 -0
- package/test/adapters/registry.test.js +89 -0
- package/test/adapters/stubs.test.js +46 -0
- package/test/cli-json.test.js +79 -0
- package/test/helpers/fake-fs.js +59 -0
- package/test-integration.sh +113 -0
- package/test-notification-plugin.kdl +34 -0
- package/test-updated-layout.sh +75 -0
- package/test-zellij-cli.sh +72 -0
- package/zellij-plugin/.cargo/config.toml +5 -0
- package/zellij-plugin/.github/workflows/ci.yml +97 -0
- package/zellij-plugin/Cargo.lock +3558 -0
- package/zellij-plugin/Cargo.toml +40 -0
- package/zellij-plugin/README.md +290 -0
- package/zellij-plugin/build.sh +179 -0
- package/zellij-plugin/configs/examples/accessibility.kdl +31 -0
- package/zellij-plugin/configs/examples/catppuccin.kdl +32 -0
- package/zellij-plugin/configs/examples/default.kdl +34 -0
- package/zellij-plugin/configs/examples/minimal.kdl +22 -0
- package/zellij-plugin/docs/CONFIGURATION.md +191 -0
- package/zellij-plugin/docs/INTEGRATION.md +333 -0
- package/zellij-plugin/src/animation.rs +451 -0
- package/zellij-plugin/src/colors.rs +407 -0
- package/zellij-plugin/src/config.rs +664 -0
- package/zellij-plugin/src/event_bridge.rs +339 -0
- package/zellij-plugin/src/main.rs +420 -0
- package/zellij-plugin/src/notification.rs +466 -0
- package/zellij-plugin/src/queue.rs +399 -0
- package/zellij-plugin/src/renderer.rs +477 -0
- package/zellij-plugin/src/state.rs +338 -0
- package/zellij-plugin/src/tests.rs +413 -0
- package/.claude/checkpoints/1756392335.json +0 -1
- package/.claude/checkpoints/1756392341.json +0 -1
- package/.claude/checkpoints/1756392347.json +0 -1
- package/.claude/checkpoints/1756392376.json +0 -1
- package/.claude/checkpoints/1756392377.json +0 -1
- package/.claude/checkpoints/1756392386.json +0 -1
- package/.claude/checkpoints/1756392387.json +0 -1
- package/.claude/checkpoints/1756392398.json +0 -1
- package/.claude/checkpoints/1756392400.json +0 -1
- package/.claude/checkpoints/1756392427.json +0 -1
- package/.claude/checkpoints/1756392428.json +0 -1
- package/.claude/checkpoints/1756392486.json +0 -1
- package/.claude/checkpoints/1756392488.json +0 -1
- package/.claude/checkpoints/1756392558.json +0 -1
- package/.claude/checkpoints/1756392559.json +0 -1
- package/.claude/checkpoints/summary-session-20250828-105040.md +0 -57
- package/.claude/checkpoints/task-1756392207.json +0 -1
- package/.claude/checkpoints/task-1756392742.json +0 -1
- package/.claude/commands/analysis/COMMAND_COMPLIANCE_REPORT.md +0 -54
- package/.claude/commands/analysis/README.md +0 -9
- package/.claude/commands/analysis/bottleneck-detect.md +0 -162
- package/.claude/commands/analysis/performance-bottlenecks.md +0 -59
- package/.claude/commands/analysis/performance-report.md +0 -25
- package/.claude/commands/analysis/token-efficiency.md +0 -45
- package/.claude/commands/analysis/token-usage.md +0 -25
- package/.claude/commands/automation/README.md +0 -9
- package/.claude/commands/automation/auto-agent.md +0 -122
- package/.claude/commands/automation/self-healing.md +0 -106
- package/.claude/commands/automation/session-memory.md +0 -90
- package/.claude/commands/automation/smart-agents.md +0 -73
- package/.claude/commands/automation/smart-spawn.md +0 -25
- package/.claude/commands/automation/workflow-select.md +0 -25
- package/.claude/commands/coordination/README.md +0 -9
- package/.claude/commands/coordination/agent-spawn.md +0 -25
- package/.claude/commands/coordination/init.md +0 -44
- package/.claude/commands/coordination/orchestrate.md +0 -43
- package/.claude/commands/coordination/spawn.md +0 -45
- package/.claude/commands/coordination/swarm-init.md +0 -85
- package/.claude/commands/coordination/task-orchestrate.md +0 -25
- package/.claude/commands/github/README.md +0 -11
- package/.claude/commands/github/code-review-swarm.md +0 -514
- package/.claude/commands/github/code-review.md +0 -25
- package/.claude/commands/github/github-modes.md +0 -147
- package/.claude/commands/github/github-swarm.md +0 -121
- package/.claude/commands/github/issue-tracker.md +0 -292
- package/.claude/commands/github/issue-triage.md +0 -25
- package/.claude/commands/github/multi-repo-swarm.md +0 -519
- package/.claude/commands/github/pr-enhance.md +0 -26
- package/.claude/commands/github/pr-manager.md +0 -170
- package/.claude/commands/github/project-board-sync.md +0 -471
- package/.claude/commands/github/release-manager.md +0 -338
- package/.claude/commands/github/release-swarm.md +0 -544
- package/.claude/commands/github/repo-analyze.md +0 -25
- package/.claude/commands/github/repo-architect.md +0 -367
- package/.claude/commands/github/swarm-issue.md +0 -482
- package/.claude/commands/github/swarm-pr.md +0 -285
- package/.claude/commands/github/sync-coordinator.md +0 -301
- package/.claude/commands/github/workflow-automation.md +0 -442
- package/.claude/commands/hooks/README.md +0 -11
- package/.claude/commands/hooks/overview.md +0 -58
- package/.claude/commands/hooks/post-edit.md +0 -117
- package/.claude/commands/hooks/post-task.md +0 -112
- package/.claude/commands/hooks/pre-edit.md +0 -113
- package/.claude/commands/hooks/pre-task.md +0 -111
- package/.claude/commands/hooks/session-end.md +0 -118
- package/.claude/commands/hooks/setup.md +0 -103
- package/.claude/commands/memory/README.md +0 -9
- package/.claude/commands/memory/memory-persist.md +0 -25
- package/.claude/commands/memory/memory-search.md +0 -25
- package/.claude/commands/memory/memory-usage.md +0 -25
- package/.claude/commands/memory/neural.md +0 -47
- package/.claude/commands/memory/usage.md +0 -46
- package/.claude/commands/monitoring/README.md +0 -9
- package/.claude/commands/monitoring/agent-metrics.md +0 -25
- package/.claude/commands/monitoring/agents.md +0 -44
- package/.claude/commands/monitoring/real-time-view.md +0 -25
- package/.claude/commands/monitoring/status.md +0 -46
- package/.claude/commands/monitoring/swarm-monitor.md +0 -25
- package/.claude/commands/optimization/README.md +0 -9
- package/.claude/commands/optimization/auto-topology.md +0 -62
- package/.claude/commands/optimization/cache-manage.md +0 -25
- package/.claude/commands/optimization/parallel-execute.md +0 -25
- package/.claude/commands/optimization/parallel-execution.md +0 -50
- package/.claude/commands/optimization/topology-optimize.md +0 -25
- package/.claude/commands/pair/README.md +0 -261
- package/.claude/commands/pair/commands.md +0 -546
- package/.claude/commands/pair/config.md +0 -510
- package/.claude/commands/pair/examples.md +0 -512
- package/.claude/commands/pair/modes.md +0 -348
- package/.claude/commands/pair/session.md +0 -407
- package/.claude/commands/pair/start.md +0 -209
- package/.claude/commands/sparc/analyzer.md +0 -52
- package/.claude/commands/sparc/architect.md +0 -53
- package/.claude/commands/sparc/batch-executor.md +0 -54
- package/.claude/commands/sparc/coder.md +0 -54
- package/.claude/commands/sparc/debugger.md +0 -54
- package/.claude/commands/sparc/designer.md +0 -53
- package/.claude/commands/sparc/documenter.md +0 -54
- package/.claude/commands/sparc/innovator.md +0 -54
- package/.claude/commands/sparc/memory-manager.md +0 -54
- package/.claude/commands/sparc/optimizer.md +0 -54
- package/.claude/commands/sparc/orchestrator.md +0 -132
- package/.claude/commands/sparc/researcher.md +0 -54
- package/.claude/commands/sparc/reviewer.md +0 -54
- package/.claude/commands/sparc/sparc-modes.md +0 -174
- package/.claude/commands/sparc/swarm-coordinator.md +0 -54
- package/.claude/commands/sparc/tdd.md +0 -54
- package/.claude/commands/sparc/tester.md +0 -54
- package/.claude/commands/sparc/workflow-manager.md +0 -54
- package/.claude/commands/stream-chain/pipeline.md +0 -121
- package/.claude/commands/stream-chain/run.md +0 -70
- package/.claude/commands/swarm/analysis.md +0 -95
- package/.claude/commands/swarm/development.md +0 -96
- package/.claude/commands/swarm/examples.md +0 -168
- package/.claude/commands/swarm/maintenance.md +0 -102
- package/.claude/commands/swarm/optimization.md +0 -117
- package/.claude/commands/swarm/research.md +0 -136
- package/.claude/commands/swarm/testing.md +0 -131
- package/.claude/commands/training/README.md +0 -9
- package/.claude/commands/training/model-update.md +0 -25
- package/.claude/commands/training/neural-patterns.md +0 -74
- package/.claude/commands/training/neural-train.md +0 -25
- package/.claude/commands/training/pattern-learn.md +0 -25
- package/.claude/commands/training/specialization.md +0 -63
- package/.claude/commands/truth/start.md +0 -143
- package/.claude/commands/verify/check.md +0 -50
- package/.claude/commands/verify/start.md +0 -128
- package/.claude/commands/workflows/README.md +0 -9
- package/.claude/commands/workflows/development.md +0 -78
- package/.claude/commands/workflows/research.md +0 -63
- package/.claude/commands/workflows/workflow-create.md +0 -25
- package/.claude/commands/workflows/workflow-execute.md +0 -25
- package/.claude/commands/workflows/workflow-export.md +0 -25
- package/.claude/config.json +0 -36
- package/.claude/settings.json +0 -162
- package/.claude-flow/metrics/agent-metrics.json +0 -1
- package/.claude-flow/metrics/performance.json +0 -9
- package/.claude-flow/metrics/system-metrics.json +0 -230
- package/.claude-flow/metrics/task-metrics.json +0 -10
- package/FIXES.md +0 -75
- 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(¬ification_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
|
+
}
|