@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,338 @@
1
+ //! State management module for Zellij Visual Notifications
2
+ //!
3
+ //! Manages visual states for panes and the overall plugin state machine.
4
+
5
+ use serde::{Deserialize, Serialize};
6
+ use crate::config::AnimationStyle;
7
+ use crate::notification::NotificationType;
8
+
9
+ /// Plugin lifecycle state
10
+ #[derive(Debug, Clone, PartialEq, Default)]
11
+ pub enum PluginState {
12
+ /// Plugin is initializing
13
+ #[default]
14
+ Initializing,
15
+ /// Plugin is initialized and waiting for permissions
16
+ Initialized,
17
+ /// Plugin is running normally
18
+ Running,
19
+ /// Plugin is in fallback mode (limited functionality)
20
+ FallbackMode,
21
+ /// Plugin encountered an error
22
+ Error(String),
23
+ /// Plugin is shutting down
24
+ ShuttingDown,
25
+ }
26
+
27
+ /// Visual state for a single pane
28
+ #[derive(Debug, Clone, Default)]
29
+ pub struct VisualState {
30
+ /// Current state of visual notification
31
+ pub state: VisualNotificationState,
32
+ /// Border color (hex string)
33
+ pub border_color: Option<String>,
34
+ /// Badge icon (Unicode character)
35
+ pub badge_icon: Option<String>,
36
+ /// Whether animation is currently active
37
+ pub is_animating: bool,
38
+ /// Animation start tick
39
+ pub animation_start_tick: u64,
40
+ /// Current animation phase (0.0 - 1.0)
41
+ pub animation_phase: f32,
42
+ /// Animation style for this notification
43
+ pub animation_style: AnimationStyle,
44
+ /// Notification message
45
+ pub notification_message: Option<String>,
46
+ /// Notification type
47
+ pub notification_type: Option<NotificationType>,
48
+ /// Timestamp when notification was received
49
+ pub notification_timestamp: u64,
50
+ /// Whether the notification has been acknowledged
51
+ pub acknowledged: bool,
52
+ /// Brightness multiplier for animation (0.0 - 1.0)
53
+ pub brightness: f32,
54
+ }
55
+
56
+ impl VisualState {
57
+ /// Create a new visual state
58
+ pub fn new() -> Self {
59
+ Self {
60
+ state: VisualNotificationState::Idle,
61
+ border_color: None,
62
+ badge_icon: None,
63
+ is_animating: false,
64
+ animation_start_tick: 0,
65
+ animation_phase: 0.0,
66
+ animation_style: AnimationStyle::Pulse,
67
+ notification_message: None,
68
+ notification_type: None,
69
+ notification_timestamp: 0,
70
+ acknowledged: false,
71
+ brightness: 1.0,
72
+ }
73
+ }
74
+
75
+ /// Clear the visual state
76
+ pub fn clear(&mut self) {
77
+ self.state = VisualNotificationState::Idle;
78
+ self.border_color = None;
79
+ self.badge_icon = None;
80
+ self.is_animating = false;
81
+ self.animation_phase = 0.0;
82
+ self.notification_message = None;
83
+ self.notification_type = None;
84
+ self.acknowledged = false;
85
+ self.brightness = 1.0;
86
+ }
87
+
88
+ /// Check if this state has an active notification
89
+ pub fn has_notification(&self) -> bool {
90
+ self.notification_type.is_some() && !self.acknowledged
91
+ }
92
+
93
+ /// Set the notification state
94
+ pub fn set_notification(
95
+ &mut self,
96
+ notification_type: NotificationType,
97
+ message: String,
98
+ border_color: String,
99
+ badge_icon: String,
100
+ ) {
101
+ self.state = VisualNotificationState::Active;
102
+ self.notification_type = Some(notification_type);
103
+ self.notification_message = Some(message);
104
+ self.border_color = Some(border_color);
105
+ self.badge_icon = Some(badge_icon);
106
+ self.acknowledged = false;
107
+ self.brightness = 1.0;
108
+ }
109
+
110
+ /// Start fading animation
111
+ pub fn start_fade(&mut self, tick: u64) {
112
+ self.state = VisualNotificationState::Fading;
113
+ self.is_animating = true;
114
+ self.animation_start_tick = tick;
115
+ self.animation_phase = 0.0;
116
+ }
117
+
118
+ /// Acknowledge the notification
119
+ pub fn acknowledge(&mut self) {
120
+ self.acknowledged = true;
121
+ self.state = VisualNotificationState::Fading;
122
+ }
123
+ }
124
+
125
+ /// Visual notification state machine states
126
+ #[derive(Debug, Clone, PartialEq, Default)]
127
+ pub enum VisualNotificationState {
128
+ /// No active notification
129
+ #[default]
130
+ Idle,
131
+ /// Notification is pending (queued)
132
+ Pending,
133
+ /// Notification is active and displayed
134
+ Active,
135
+ /// Notification is fading out
136
+ Fading,
137
+ /// Error state
138
+ Error,
139
+ }
140
+
141
+ impl VisualNotificationState {
142
+ /// Check if state allows transitions
143
+ pub fn can_transition_to(&self, target: &VisualNotificationState) -> bool {
144
+ match (self, target) {
145
+ // From Idle
146
+ (VisualNotificationState::Idle, VisualNotificationState::Pending) => true,
147
+ (VisualNotificationState::Idle, VisualNotificationState::Active) => true,
148
+ // From Pending
149
+ (VisualNotificationState::Pending, VisualNotificationState::Active) => true,
150
+ (VisualNotificationState::Pending, VisualNotificationState::Idle) => true, // Cancel
151
+ // From Active
152
+ (VisualNotificationState::Active, VisualNotificationState::Fading) => true,
153
+ (VisualNotificationState::Active, VisualNotificationState::Idle) => true, // Instant clear
154
+ // From Fading
155
+ (VisualNotificationState::Fading, VisualNotificationState::Idle) => true,
156
+ (VisualNotificationState::Fading, VisualNotificationState::Active) => true, // New notification
157
+ // From Error
158
+ (VisualNotificationState::Error, VisualNotificationState::Idle) => true,
159
+ (VisualNotificationState::Error, VisualNotificationState::Active) => true,
160
+ // Same state (no-op)
161
+ (a, b) if a == b => true,
162
+ // All other transitions are invalid
163
+ _ => false,
164
+ }
165
+ }
166
+
167
+ /// Get the display name for this state
168
+ pub fn display_name(&self) -> &'static str {
169
+ match self {
170
+ VisualNotificationState::Idle => "Idle",
171
+ VisualNotificationState::Pending => "Pending",
172
+ VisualNotificationState::Active => "Active",
173
+ VisualNotificationState::Fading => "Fading",
174
+ VisualNotificationState::Error => "Error",
175
+ }
176
+ }
177
+ }
178
+
179
+ /// State transition event
180
+ #[derive(Debug, Clone)]
181
+ pub struct StateTransition {
182
+ /// Source state
183
+ pub from: VisualNotificationState,
184
+ /// Target state
185
+ pub to: VisualNotificationState,
186
+ /// Timestamp of transition
187
+ pub timestamp: u64,
188
+ /// Reason for transition
189
+ pub reason: String,
190
+ }
191
+
192
+ impl StateTransition {
193
+ /// Create a new state transition
194
+ pub fn new(from: VisualNotificationState, to: VisualNotificationState, reason: &str) -> Self {
195
+ Self {
196
+ from,
197
+ to,
198
+ timestamp: 0, // Will be set by the caller
199
+ reason: reason.to_string(),
200
+ }
201
+ }
202
+ }
203
+
204
+ /// State manager for tracking multiple pane states
205
+ #[derive(Debug, Default)]
206
+ pub struct StateManager {
207
+ /// History of state transitions (for debugging)
208
+ transition_history: Vec<StateTransition>,
209
+ /// Maximum history size
210
+ max_history_size: usize,
211
+ }
212
+
213
+ impl StateManager {
214
+ /// Create a new state manager
215
+ pub fn new() -> Self {
216
+ Self {
217
+ transition_history: Vec::new(),
218
+ max_history_size: 100,
219
+ }
220
+ }
221
+
222
+ /// Record a state transition
223
+ pub fn record_transition(&mut self, transition: StateTransition) {
224
+ self.transition_history.push(transition);
225
+
226
+ // Keep history bounded
227
+ while self.transition_history.len() > self.max_history_size {
228
+ self.transition_history.remove(0);
229
+ }
230
+ }
231
+
232
+ /// Get recent transitions
233
+ pub fn recent_transitions(&self, count: usize) -> &[StateTransition] {
234
+ let start = if self.transition_history.len() > count {
235
+ self.transition_history.len() - count
236
+ } else {
237
+ 0
238
+ };
239
+ &self.transition_history[start..]
240
+ }
241
+
242
+ /// Clear transition history
243
+ pub fn clear_history(&mut self) {
244
+ self.transition_history.clear();
245
+ }
246
+ }
247
+
248
+ /// Pane-specific notification state for synchronization
249
+ #[derive(Debug, Clone, Serialize, Deserialize)]
250
+ pub struct PaneNotificationState {
251
+ /// Pane ID
252
+ pub pane_id: u32,
253
+ /// Current visual state name
254
+ pub state: String,
255
+ /// Active notification type (if any)
256
+ pub notification_type: Option<String>,
257
+ /// Active notification message (if any)
258
+ pub notification_message: Option<String>,
259
+ /// Whether notification is acknowledged
260
+ pub acknowledged: bool,
261
+ /// Timestamp of last update
262
+ pub last_update: u64,
263
+ }
264
+
265
+ impl From<&VisualState> for PaneNotificationState {
266
+ fn from(state: &VisualState) -> Self {
267
+ Self {
268
+ pane_id: 0, // Will be set by caller
269
+ state: state.state.display_name().to_string(),
270
+ notification_type: state.notification_type.as_ref().map(|t| t.name().to_string()),
271
+ notification_message: state.notification_message.clone(),
272
+ acknowledged: state.acknowledged,
273
+ last_update: state.notification_timestamp,
274
+ }
275
+ }
276
+ }
277
+
278
+ #[cfg(test)]
279
+ mod tests {
280
+ use super::*;
281
+
282
+ #[test]
283
+ fn test_visual_state_default() {
284
+ let state = VisualState::default();
285
+ assert_eq!(state.state, VisualNotificationState::Idle);
286
+ assert!(!state.has_notification());
287
+ }
288
+
289
+ #[test]
290
+ fn test_visual_state_clear() {
291
+ let mut state = VisualState::new();
292
+ state.border_color = Some("#ff0000".to_string());
293
+ state.badge_icon = Some("!".to_string());
294
+ state.is_animating = true;
295
+
296
+ state.clear();
297
+
298
+ assert_eq!(state.state, VisualNotificationState::Idle);
299
+ assert!(state.border_color.is_none());
300
+ assert!(state.badge_icon.is_none());
301
+ assert!(!state.is_animating);
302
+ }
303
+
304
+ #[test]
305
+ fn test_state_transitions() {
306
+ let idle = VisualNotificationState::Idle;
307
+ let pending = VisualNotificationState::Pending;
308
+ let active = VisualNotificationState::Active;
309
+ let fading = VisualNotificationState::Fading;
310
+
311
+ assert!(idle.can_transition_to(&pending));
312
+ assert!(idle.can_transition_to(&active));
313
+ assert!(pending.can_transition_to(&active));
314
+ assert!(active.can_transition_to(&fading));
315
+ assert!(fading.can_transition_to(&idle));
316
+
317
+ // Invalid transitions
318
+ assert!(!pending.can_transition_to(&fading));
319
+ assert!(!idle.can_transition_to(&fading));
320
+ }
321
+
322
+ #[test]
323
+ fn test_state_manager_history() {
324
+ let mut manager = StateManager::new();
325
+
326
+ for i in 0..10 {
327
+ let transition = StateTransition::new(
328
+ VisualNotificationState::Idle,
329
+ VisualNotificationState::Active,
330
+ &format!("Test {}", i),
331
+ );
332
+ manager.record_transition(transition);
333
+ }
334
+
335
+ let recent = manager.recent_transitions(5);
336
+ assert_eq!(recent.len(), 5);
337
+ }
338
+ }