@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,664 @@
|
|
|
1
|
+
//! Configuration module for Zellij Visual Notifications
|
|
2
|
+
//!
|
|
3
|
+
//! Handles KDL configuration parsing, validation, and hot-reload functionality.
|
|
4
|
+
|
|
5
|
+
use serde::{Deserialize, Serialize};
|
|
6
|
+
use std::collections::BTreeMap;
|
|
7
|
+
|
|
8
|
+
/// Main plugin configuration
|
|
9
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
10
|
+
pub struct Config {
|
|
11
|
+
/// Enable/disable the plugin
|
|
12
|
+
pub enabled: bool,
|
|
13
|
+
/// Theme configuration
|
|
14
|
+
pub theme: ThemeConfig,
|
|
15
|
+
/// Animation configuration
|
|
16
|
+
pub animation: AnimationConfig,
|
|
17
|
+
/// Accessibility configuration
|
|
18
|
+
pub accessibility: AccessibilityConfig,
|
|
19
|
+
/// Notification timeout in milliseconds
|
|
20
|
+
pub notification_timeout_ms: u64,
|
|
21
|
+
/// Maximum queue size
|
|
22
|
+
pub queue_max_size: usize,
|
|
23
|
+
/// Enable status bar widget
|
|
24
|
+
pub show_status_bar: bool,
|
|
25
|
+
/// Enable pane border colors
|
|
26
|
+
pub show_border_colors: bool,
|
|
27
|
+
/// Enable tab badges
|
|
28
|
+
pub show_tab_badges: bool,
|
|
29
|
+
/// IPC socket path (for external communication)
|
|
30
|
+
pub ipc_socket_path: Option<String>,
|
|
31
|
+
/// Debug mode
|
|
32
|
+
pub debug: bool,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
impl Default for Config {
|
|
36
|
+
fn default() -> Self {
|
|
37
|
+
Self {
|
|
38
|
+
enabled: true,
|
|
39
|
+
theme: ThemeConfig::default(),
|
|
40
|
+
animation: AnimationConfig::default(),
|
|
41
|
+
accessibility: AccessibilityConfig::default(),
|
|
42
|
+
notification_timeout_ms: 300_000, // 5 minutes
|
|
43
|
+
queue_max_size: 100,
|
|
44
|
+
show_status_bar: true,
|
|
45
|
+
show_border_colors: true,
|
|
46
|
+
show_tab_badges: true,
|
|
47
|
+
ipc_socket_path: None,
|
|
48
|
+
debug: false,
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
impl Config {
|
|
54
|
+
/// Create configuration from Zellij plugin configuration map
|
|
55
|
+
pub fn from_plugin_config(config_map: &BTreeMap<String, String>) -> Self {
|
|
56
|
+
let mut config = Config::default();
|
|
57
|
+
|
|
58
|
+
// Parse boolean options
|
|
59
|
+
if let Some(enabled) = config_map.get("enabled") {
|
|
60
|
+
config.enabled = enabled.parse().unwrap_or(true);
|
|
61
|
+
}
|
|
62
|
+
if let Some(debug) = config_map.get("debug") {
|
|
63
|
+
config.debug = debug.parse().unwrap_or(false);
|
|
64
|
+
}
|
|
65
|
+
if let Some(show_status_bar) = config_map.get("show_status_bar") {
|
|
66
|
+
config.show_status_bar = show_status_bar.parse().unwrap_or(true);
|
|
67
|
+
}
|
|
68
|
+
if let Some(show_border_colors) = config_map.get("show_border_colors") {
|
|
69
|
+
config.show_border_colors = show_border_colors.parse().unwrap_or(true);
|
|
70
|
+
}
|
|
71
|
+
if let Some(show_tab_badges) = config_map.get("show_tab_badges") {
|
|
72
|
+
config.show_tab_badges = show_tab_badges.parse().unwrap_or(true);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Parse numeric options
|
|
76
|
+
if let Some(timeout) = config_map.get("notification_timeout_ms") {
|
|
77
|
+
config.notification_timeout_ms = timeout.parse().unwrap_or(300_000);
|
|
78
|
+
}
|
|
79
|
+
if let Some(max_size) = config_map.get("queue_max_size") {
|
|
80
|
+
config.queue_max_size = max_size.parse().unwrap_or(100);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Parse theme
|
|
84
|
+
if let Some(theme_name) = config_map.get("theme") {
|
|
85
|
+
config.theme = ThemeConfig::from_preset(theme_name);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Parse individual colors
|
|
89
|
+
if let Some(success_color) = config_map.get("success_color") {
|
|
90
|
+
config.theme.success_color = success_color.clone();
|
|
91
|
+
}
|
|
92
|
+
if let Some(error_color) = config_map.get("error_color") {
|
|
93
|
+
config.theme.error_color = error_color.clone();
|
|
94
|
+
}
|
|
95
|
+
if let Some(warning_color) = config_map.get("warning_color") {
|
|
96
|
+
config.theme.warning_color = warning_color.clone();
|
|
97
|
+
}
|
|
98
|
+
if let Some(info_color) = config_map.get("info_color") {
|
|
99
|
+
config.theme.info_color = info_color.clone();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Parse animation settings
|
|
103
|
+
if let Some(animation_enabled) = config_map.get("animation_enabled") {
|
|
104
|
+
config.animation.enabled = animation_enabled.parse().unwrap_or(true);
|
|
105
|
+
}
|
|
106
|
+
if let Some(animation_style) = config_map.get("animation_style") {
|
|
107
|
+
config.animation.style = AnimationStyle::from_str(animation_style);
|
|
108
|
+
}
|
|
109
|
+
if let Some(animation_speed) = config_map.get("animation_speed") {
|
|
110
|
+
config.animation.speed = animation_speed.parse().unwrap_or(50);
|
|
111
|
+
}
|
|
112
|
+
if let Some(animation_cycles) = config_map.get("animation_cycles") {
|
|
113
|
+
config.animation.cycles = animation_cycles.parse().unwrap_or(3);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Parse accessibility settings
|
|
117
|
+
if let Some(high_contrast) = config_map.get("high_contrast") {
|
|
118
|
+
config.accessibility.high_contrast = high_contrast.parse().unwrap_or(false);
|
|
119
|
+
}
|
|
120
|
+
if let Some(reduced_motion) = config_map.get("reduced_motion") {
|
|
121
|
+
config.accessibility.reduced_motion = reduced_motion.parse().unwrap_or(false);
|
|
122
|
+
if config.accessibility.reduced_motion {
|
|
123
|
+
config.animation.enabled = false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Parse IPC socket path
|
|
128
|
+
if let Some(ipc_path) = config_map.get("ipc_socket_path") {
|
|
129
|
+
config.ipc_socket_path = Some(ipc_path.clone());
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
config
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/// Validate the configuration
|
|
136
|
+
pub fn validate(&self) -> Result<(), String> {
|
|
137
|
+
if self.notification_timeout_ms < 1000 {
|
|
138
|
+
return Err("notification_timeout_ms must be at least 1000ms".to_string());
|
|
139
|
+
}
|
|
140
|
+
if self.queue_max_size < 1 {
|
|
141
|
+
return Err("queue_max_size must be at least 1".to_string());
|
|
142
|
+
}
|
|
143
|
+
if self.animation.speed < 1 || self.animation.speed > 100 {
|
|
144
|
+
return Err("animation_speed must be between 1 and 100".to_string());
|
|
145
|
+
}
|
|
146
|
+
if self.animation.cycles < 1 || self.animation.cycles > 10 {
|
|
147
|
+
return Err("animation_cycles must be between 1 and 10".to_string());
|
|
148
|
+
}
|
|
149
|
+
Ok(())
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/// Theme configuration
|
|
154
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
155
|
+
pub struct ThemeConfig {
|
|
156
|
+
/// Theme name/preset
|
|
157
|
+
pub name: String,
|
|
158
|
+
/// Success notification color (green by default)
|
|
159
|
+
pub success_color: String,
|
|
160
|
+
/// Error notification color (red by default)
|
|
161
|
+
pub error_color: String,
|
|
162
|
+
/// Warning notification color (yellow by default)
|
|
163
|
+
pub warning_color: String,
|
|
164
|
+
/// Info notification color (blue by default)
|
|
165
|
+
pub info_color: String,
|
|
166
|
+
/// Background color for status bar
|
|
167
|
+
pub background_color: String,
|
|
168
|
+
/// Foreground/text color
|
|
169
|
+
pub foreground_color: String,
|
|
170
|
+
/// Border highlight color
|
|
171
|
+
pub highlight_color: String,
|
|
172
|
+
/// Dimmed/muted color
|
|
173
|
+
pub dimmed_color: String,
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
impl Default for ThemeConfig {
|
|
177
|
+
fn default() -> Self {
|
|
178
|
+
Self {
|
|
179
|
+
name: "default".to_string(),
|
|
180
|
+
success_color: "#22c55e".to_string(), // Green
|
|
181
|
+
error_color: "#ef4444".to_string(), // Red
|
|
182
|
+
warning_color: "#eab308".to_string(), // Yellow
|
|
183
|
+
info_color: "#3b82f6".to_string(), // Blue
|
|
184
|
+
background_color: "#1e1e2e".to_string(),
|
|
185
|
+
foreground_color: "#cdd6f4".to_string(),
|
|
186
|
+
highlight_color: "#89b4fa".to_string(),
|
|
187
|
+
dimmed_color: "#6c7086".to_string(),
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
impl ThemeConfig {
|
|
193
|
+
/// Create a theme from a preset name
|
|
194
|
+
pub fn from_preset(name: &str) -> Self {
|
|
195
|
+
match name.to_lowercase().as_str() {
|
|
196
|
+
"dracula" => Self::dracula(),
|
|
197
|
+
"nord" => Self::nord(),
|
|
198
|
+
"solarized" | "solarized-dark" => Self::solarized_dark(),
|
|
199
|
+
"solarized-light" => Self::solarized_light(),
|
|
200
|
+
"catppuccin" | "catppuccin-mocha" => Self::catppuccin_mocha(),
|
|
201
|
+
"catppuccin-latte" => Self::catppuccin_latte(),
|
|
202
|
+
"gruvbox" | "gruvbox-dark" => Self::gruvbox_dark(),
|
|
203
|
+
"gruvbox-light" => Self::gruvbox_light(),
|
|
204
|
+
"tokyo-night" => Self::tokyo_night(),
|
|
205
|
+
"one-dark" => Self::one_dark(),
|
|
206
|
+
_ => Self::default(),
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/// Dracula theme
|
|
211
|
+
fn dracula() -> Self {
|
|
212
|
+
Self {
|
|
213
|
+
name: "dracula".to_string(),
|
|
214
|
+
success_color: "#50fa7b".to_string(),
|
|
215
|
+
error_color: "#ff5555".to_string(),
|
|
216
|
+
warning_color: "#f1fa8c".to_string(),
|
|
217
|
+
info_color: "#8be9fd".to_string(),
|
|
218
|
+
background_color: "#282a36".to_string(),
|
|
219
|
+
foreground_color: "#f8f8f2".to_string(),
|
|
220
|
+
highlight_color: "#bd93f9".to_string(),
|
|
221
|
+
dimmed_color: "#6272a4".to_string(),
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/// Nord theme
|
|
226
|
+
fn nord() -> Self {
|
|
227
|
+
Self {
|
|
228
|
+
name: "nord".to_string(),
|
|
229
|
+
success_color: "#a3be8c".to_string(),
|
|
230
|
+
error_color: "#bf616a".to_string(),
|
|
231
|
+
warning_color: "#ebcb8b".to_string(),
|
|
232
|
+
info_color: "#81a1c1".to_string(),
|
|
233
|
+
background_color: "#2e3440".to_string(),
|
|
234
|
+
foreground_color: "#eceff4".to_string(),
|
|
235
|
+
highlight_color: "#88c0d0".to_string(),
|
|
236
|
+
dimmed_color: "#4c566a".to_string(),
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/// Solarized Dark theme
|
|
241
|
+
fn solarized_dark() -> Self {
|
|
242
|
+
Self {
|
|
243
|
+
name: "solarized-dark".to_string(),
|
|
244
|
+
success_color: "#859900".to_string(),
|
|
245
|
+
error_color: "#dc322f".to_string(),
|
|
246
|
+
warning_color: "#b58900".to_string(),
|
|
247
|
+
info_color: "#268bd2".to_string(),
|
|
248
|
+
background_color: "#002b36".to_string(),
|
|
249
|
+
foreground_color: "#839496".to_string(),
|
|
250
|
+
highlight_color: "#2aa198".to_string(),
|
|
251
|
+
dimmed_color: "#586e75".to_string(),
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/// Solarized Light theme
|
|
256
|
+
fn solarized_light() -> Self {
|
|
257
|
+
Self {
|
|
258
|
+
name: "solarized-light".to_string(),
|
|
259
|
+
success_color: "#859900".to_string(),
|
|
260
|
+
error_color: "#dc322f".to_string(),
|
|
261
|
+
warning_color: "#b58900".to_string(),
|
|
262
|
+
info_color: "#268bd2".to_string(),
|
|
263
|
+
background_color: "#fdf6e3".to_string(),
|
|
264
|
+
foreground_color: "#657b83".to_string(),
|
|
265
|
+
highlight_color: "#2aa198".to_string(),
|
|
266
|
+
dimmed_color: "#93a1a1".to_string(),
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/// Catppuccin Mocha theme
|
|
271
|
+
fn catppuccin_mocha() -> Self {
|
|
272
|
+
Self {
|
|
273
|
+
name: "catppuccin-mocha".to_string(),
|
|
274
|
+
success_color: "#a6e3a1".to_string(),
|
|
275
|
+
error_color: "#f38ba8".to_string(),
|
|
276
|
+
warning_color: "#f9e2af".to_string(),
|
|
277
|
+
info_color: "#89b4fa".to_string(),
|
|
278
|
+
background_color: "#1e1e2e".to_string(),
|
|
279
|
+
foreground_color: "#cdd6f4".to_string(),
|
|
280
|
+
highlight_color: "#cba6f7".to_string(),
|
|
281
|
+
dimmed_color: "#6c7086".to_string(),
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/// Catppuccin Latte theme (light)
|
|
286
|
+
fn catppuccin_latte() -> Self {
|
|
287
|
+
Self {
|
|
288
|
+
name: "catppuccin-latte".to_string(),
|
|
289
|
+
success_color: "#40a02b".to_string(),
|
|
290
|
+
error_color: "#d20f39".to_string(),
|
|
291
|
+
warning_color: "#df8e1d".to_string(),
|
|
292
|
+
info_color: "#1e66f5".to_string(),
|
|
293
|
+
background_color: "#eff1f5".to_string(),
|
|
294
|
+
foreground_color: "#4c4f69".to_string(),
|
|
295
|
+
highlight_color: "#8839ef".to_string(),
|
|
296
|
+
dimmed_color: "#9ca0b0".to_string(),
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/// Gruvbox Dark theme
|
|
301
|
+
fn gruvbox_dark() -> Self {
|
|
302
|
+
Self {
|
|
303
|
+
name: "gruvbox-dark".to_string(),
|
|
304
|
+
success_color: "#b8bb26".to_string(),
|
|
305
|
+
error_color: "#fb4934".to_string(),
|
|
306
|
+
warning_color: "#fabd2f".to_string(),
|
|
307
|
+
info_color: "#83a598".to_string(),
|
|
308
|
+
background_color: "#282828".to_string(),
|
|
309
|
+
foreground_color: "#ebdbb2".to_string(),
|
|
310
|
+
highlight_color: "#d3869b".to_string(),
|
|
311
|
+
dimmed_color: "#928374".to_string(),
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/// Gruvbox Light theme
|
|
316
|
+
fn gruvbox_light() -> Self {
|
|
317
|
+
Self {
|
|
318
|
+
name: "gruvbox-light".to_string(),
|
|
319
|
+
success_color: "#79740e".to_string(),
|
|
320
|
+
error_color: "#9d0006".to_string(),
|
|
321
|
+
warning_color: "#b57614".to_string(),
|
|
322
|
+
info_color: "#076678".to_string(),
|
|
323
|
+
background_color: "#fbf1c7".to_string(),
|
|
324
|
+
foreground_color: "#3c3836".to_string(),
|
|
325
|
+
highlight_color: "#8f3f71".to_string(),
|
|
326
|
+
dimmed_color: "#928374".to_string(),
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/// Tokyo Night theme
|
|
331
|
+
fn tokyo_night() -> Self {
|
|
332
|
+
Self {
|
|
333
|
+
name: "tokyo-night".to_string(),
|
|
334
|
+
success_color: "#9ece6a".to_string(),
|
|
335
|
+
error_color: "#f7768e".to_string(),
|
|
336
|
+
warning_color: "#e0af68".to_string(),
|
|
337
|
+
info_color: "#7aa2f7".to_string(),
|
|
338
|
+
background_color: "#1a1b26".to_string(),
|
|
339
|
+
foreground_color: "#c0caf5".to_string(),
|
|
340
|
+
highlight_color: "#bb9af7".to_string(),
|
|
341
|
+
dimmed_color: "#565f89".to_string(),
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/// One Dark theme
|
|
346
|
+
fn one_dark() -> Self {
|
|
347
|
+
Self {
|
|
348
|
+
name: "one-dark".to_string(),
|
|
349
|
+
success_color: "#98c379".to_string(),
|
|
350
|
+
error_color: "#e06c75".to_string(),
|
|
351
|
+
warning_color: "#e5c07b".to_string(),
|
|
352
|
+
info_color: "#61afef".to_string(),
|
|
353
|
+
background_color: "#282c34".to_string(),
|
|
354
|
+
foreground_color: "#abb2bf".to_string(),
|
|
355
|
+
highlight_color: "#c678dd".to_string(),
|
|
356
|
+
dimmed_color: "#5c6370".to_string(),
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/// Animation configuration
|
|
362
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
363
|
+
pub struct AnimationConfig {
|
|
364
|
+
/// Enable/disable animations
|
|
365
|
+
pub enabled: bool,
|
|
366
|
+
/// Animation style
|
|
367
|
+
pub style: AnimationStyle,
|
|
368
|
+
/// Animation speed (1-100, higher = faster)
|
|
369
|
+
pub speed: u8,
|
|
370
|
+
/// Number of animation cycles
|
|
371
|
+
pub cycles: u8,
|
|
372
|
+
/// Duration in milliseconds
|
|
373
|
+
pub duration_ms: u64,
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
impl Default for AnimationConfig {
|
|
377
|
+
fn default() -> Self {
|
|
378
|
+
Self {
|
|
379
|
+
enabled: true,
|
|
380
|
+
style: AnimationStyle::Pulse,
|
|
381
|
+
speed: 50,
|
|
382
|
+
cycles: 3,
|
|
383
|
+
duration_ms: 2000,
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/// Animation styles
|
|
389
|
+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
390
|
+
pub enum AnimationStyle {
|
|
391
|
+
/// Pulse animation (gentle fade in/out)
|
|
392
|
+
Pulse,
|
|
393
|
+
/// Flash animation (quick blink)
|
|
394
|
+
Flash,
|
|
395
|
+
/// Fade animation (slow fade out)
|
|
396
|
+
Fade,
|
|
397
|
+
/// Breathe animation (smooth sine wave)
|
|
398
|
+
Breathe,
|
|
399
|
+
/// None (static, no animation)
|
|
400
|
+
None,
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
impl Default for AnimationStyle {
|
|
404
|
+
fn default() -> Self {
|
|
405
|
+
Self::Pulse
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
impl AnimationStyle {
|
|
410
|
+
/// Parse animation style from string
|
|
411
|
+
pub fn from_str(s: &str) -> Self {
|
|
412
|
+
match s.to_lowercase().as_str() {
|
|
413
|
+
"pulse" => Self::Pulse,
|
|
414
|
+
"flash" => Self::Flash,
|
|
415
|
+
"fade" => Self::Fade,
|
|
416
|
+
"breathe" => Self::Breathe,
|
|
417
|
+
"none" | "disabled" => Self::None,
|
|
418
|
+
_ => Self::Pulse,
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/// Accessibility configuration
|
|
424
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
425
|
+
pub struct AccessibilityConfig {
|
|
426
|
+
/// Enable high contrast mode
|
|
427
|
+
pub high_contrast: bool,
|
|
428
|
+
/// Enable reduced motion mode (disables animations)
|
|
429
|
+
pub reduced_motion: bool,
|
|
430
|
+
/// Enable screen reader announcements
|
|
431
|
+
pub screen_reader: bool,
|
|
432
|
+
/// Use patterns in addition to colors
|
|
433
|
+
pub use_patterns: bool,
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
impl Default for AccessibilityConfig {
|
|
437
|
+
fn default() -> Self {
|
|
438
|
+
Self {
|
|
439
|
+
high_contrast: false,
|
|
440
|
+
reduced_motion: false,
|
|
441
|
+
screen_reader: false,
|
|
442
|
+
use_patterns: true,
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/// Configuration manager for hot-reload
|
|
448
|
+
#[derive(Default)]
|
|
449
|
+
pub struct ConfigManager {
|
|
450
|
+
/// Last known configuration
|
|
451
|
+
last_config: Option<Config>,
|
|
452
|
+
/// Configuration file path
|
|
453
|
+
config_path: Option<String>,
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
impl ConfigManager {
|
|
457
|
+
/// Create a new configuration manager
|
|
458
|
+
pub fn new() -> Self {
|
|
459
|
+
Self {
|
|
460
|
+
last_config: None,
|
|
461
|
+
config_path: None,
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/// Set the configuration file path
|
|
466
|
+
pub fn set_path(&mut self, path: &str) {
|
|
467
|
+
self.config_path = Some(path.to_string());
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/// Reload configuration from file
|
|
471
|
+
pub fn reload(&mut self) -> Option<Config> {
|
|
472
|
+
// In WASM environment, we can't directly read files
|
|
473
|
+
// This would need to be triggered by a custom message from the host
|
|
474
|
+
// For now, return None to indicate no change
|
|
475
|
+
None
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/// Parse KDL configuration string
|
|
479
|
+
pub fn parse_kdl(&self, content: &str) -> Result<Config, String> {
|
|
480
|
+
// Parse KDL content (kdl 4.x uses str::parse)
|
|
481
|
+
let doc: kdl::KdlDocument = content.parse()
|
|
482
|
+
.map_err(|e: kdl::KdlError| format!("KDL parse error: {}", e))?;
|
|
483
|
+
|
|
484
|
+
let mut config = Config::default();
|
|
485
|
+
|
|
486
|
+
// Parse the document
|
|
487
|
+
for node in doc.nodes() {
|
|
488
|
+
match node.name().value() {
|
|
489
|
+
"enabled" => {
|
|
490
|
+
if let Some(val) = node.get(0) {
|
|
491
|
+
config.enabled = val.value().as_bool().unwrap_or(true);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
"theme" => {
|
|
495
|
+
if let Some(val) = node.get(0) {
|
|
496
|
+
if let Some(name) = val.value().as_string() {
|
|
497
|
+
config.theme = ThemeConfig::from_preset(name);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
// Parse nested theme properties
|
|
501
|
+
if let Some(children) = node.children() {
|
|
502
|
+
for child in children.nodes() {
|
|
503
|
+
match child.name().value() {
|
|
504
|
+
"success_color" => {
|
|
505
|
+
if let Some(val) = child.get(0) {
|
|
506
|
+
if let Some(color) = val.value().as_string() {
|
|
507
|
+
config.theme.success_color = color.to_string();
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
"error_color" => {
|
|
512
|
+
if let Some(val) = child.get(0) {
|
|
513
|
+
if let Some(color) = val.value().as_string() {
|
|
514
|
+
config.theme.error_color = color.to_string();
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
"warning_color" => {
|
|
519
|
+
if let Some(val) = child.get(0) {
|
|
520
|
+
if let Some(color) = val.value().as_string() {
|
|
521
|
+
config.theme.warning_color = color.to_string();
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
"info_color" => {
|
|
526
|
+
if let Some(val) = child.get(0) {
|
|
527
|
+
if let Some(color) = val.value().as_string() {
|
|
528
|
+
config.theme.info_color = color.to_string();
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
_ => {}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
"animation" => {
|
|
538
|
+
if let Some(children) = node.children() {
|
|
539
|
+
for child in children.nodes() {
|
|
540
|
+
match child.name().value() {
|
|
541
|
+
"enabled" => {
|
|
542
|
+
if let Some(val) = child.get(0) {
|
|
543
|
+
config.animation.enabled = val.value().as_bool().unwrap_or(true);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
"style" => {
|
|
547
|
+
if let Some(val) = child.get(0) {
|
|
548
|
+
if let Some(style) = val.value().as_string() {
|
|
549
|
+
config.animation.style = AnimationStyle::from_str(style);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
"speed" => {
|
|
554
|
+
if let Some(val) = child.get(0) {
|
|
555
|
+
if let Some(speed) = val.value().as_i64() {
|
|
556
|
+
config.animation.speed = speed.clamp(1, 100) as u8;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
"cycles" => {
|
|
561
|
+
if let Some(val) = child.get(0) {
|
|
562
|
+
if let Some(cycles) = val.value().as_i64() {
|
|
563
|
+
config.animation.cycles = cycles.clamp(1, 10) as u8;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
_ => {}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
"accessibility" => {
|
|
573
|
+
if let Some(children) = node.children() {
|
|
574
|
+
for child in children.nodes() {
|
|
575
|
+
match child.name().value() {
|
|
576
|
+
"high_contrast" => {
|
|
577
|
+
if let Some(val) = child.get(0) {
|
|
578
|
+
config.accessibility.high_contrast = val.value().as_bool().unwrap_or(false);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
"reduced_motion" => {
|
|
582
|
+
if let Some(val) = child.get(0) {
|
|
583
|
+
config.accessibility.reduced_motion = val.value().as_bool().unwrap_or(false);
|
|
584
|
+
if config.accessibility.reduced_motion {
|
|
585
|
+
config.animation.enabled = false;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
_ => {}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
"notification_timeout_ms" => {
|
|
595
|
+
if let Some(val) = node.get(0) {
|
|
596
|
+
if let Some(timeout) = val.value().as_i64() {
|
|
597
|
+
config.notification_timeout_ms = timeout.max(1000) as u64;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
"queue_max_size" => {
|
|
602
|
+
if let Some(val) = node.get(0) {
|
|
603
|
+
if let Some(size) = val.value().as_i64() {
|
|
604
|
+
config.queue_max_size = size.max(1) as usize;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
_ => {}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
config.validate()?;
|
|
613
|
+
Ok(config)
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
#[cfg(test)]
|
|
618
|
+
mod tests {
|
|
619
|
+
use super::*;
|
|
620
|
+
|
|
621
|
+
#[test]
|
|
622
|
+
fn test_default_config() {
|
|
623
|
+
let config = Config::default();
|
|
624
|
+
assert!(config.enabled);
|
|
625
|
+
assert!(config.animation.enabled);
|
|
626
|
+
assert_eq!(config.animation.style, AnimationStyle::Pulse);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
#[test]
|
|
630
|
+
fn test_theme_presets() {
|
|
631
|
+
let themes = vec![
|
|
632
|
+
"dracula", "nord", "solarized", "catppuccin", "gruvbox", "tokyo-night", "one-dark"
|
|
633
|
+
];
|
|
634
|
+
|
|
635
|
+
for theme_name in themes {
|
|
636
|
+
let theme = ThemeConfig::from_preset(theme_name);
|
|
637
|
+
assert!(!theme.success_color.is_empty());
|
|
638
|
+
assert!(!theme.error_color.is_empty());
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
#[test]
|
|
643
|
+
fn test_config_validation() {
|
|
644
|
+
let mut config = Config::default();
|
|
645
|
+
assert!(config.validate().is_ok());
|
|
646
|
+
|
|
647
|
+
config.notification_timeout_ms = 100;
|
|
648
|
+
assert!(config.validate().is_err());
|
|
649
|
+
|
|
650
|
+
config.notification_timeout_ms = 5000;
|
|
651
|
+
config.queue_max_size = 0;
|
|
652
|
+
assert!(config.validate().is_err());
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
#[test]
|
|
656
|
+
fn test_animation_style_parsing() {
|
|
657
|
+
assert_eq!(AnimationStyle::from_str("pulse"), AnimationStyle::Pulse);
|
|
658
|
+
assert_eq!(AnimationStyle::from_str("FLASH"), AnimationStyle::Flash);
|
|
659
|
+
assert_eq!(AnimationStyle::from_str("fade"), AnimationStyle::Fade);
|
|
660
|
+
assert_eq!(AnimationStyle::from_str("breathe"), AnimationStyle::Breathe);
|
|
661
|
+
assert_eq!(AnimationStyle::from_str("none"), AnimationStyle::None);
|
|
662
|
+
assert_eq!(AnimationStyle::from_str("invalid"), AnimationStyle::Pulse);
|
|
663
|
+
}
|
|
664
|
+
}
|