generated from nhcarrigan/template
f173892aaa
## Summary This PR includes major feature additions, bug fixes, comprehensive testing improvements, and responsive design enhancements! ## New Features โจ ### Plugin & MCP Management (#133, #134) - **Plugin Management Panel**: Install, uninstall, enable/disable, and update plugins - **MCP Server Management Panel**: Add/remove MCP servers, view detailed configuration - **Marketplace Management**: Add/remove plugin marketplaces from GitHub - Backend commands for full CLI integration (`list_plugins`, `install_plugin`, `add_mcp_server`, etc.) - Beautiful UI with proper loading states, error handling, and theme support ### Visual Todo List Panel (#132) - Real-time todo list display when Hikari uses the `TodoWrite` tool - Shows pending/in-progress/completed status with visual indicators - Progress bar and completion count - Automatically clears on disconnect - Theme-aware styling ### Clear Session History Button (#130) - "Clear All Sessions" button in Session History panel - Confirmation dialog with session count - Keyboard support and accessibility features - Gives users control over disk usage ### CLI Version Display (#131) - Displays Claude CLI version in status bar - Auto-polls every 30 seconds for updates - Useful for debugging and feature compatibility ## Bug Fixes ๐ ### Stats Panel Scrolling (#136) - **Fixed stats panel overflow**: Added scrollable container with `max-height` constraint - Stats panel now scrolls when content (Tools Used, Historical Costs, Budget sections) gets too long - Prevents content from overflowing off screen ### Agent Monitor Fixes (#122) - **Fixed agents stuck in "running" state**: Added `SubagentStop` hook parsing - **Fixed agents persisting after disconnect**: Call `clearConversation()` on disconnect - **Fixed "Kill All" button**: Now properly marks all agents as errored - **Fixed badge persisting after tab close**: Cleanup agents when conversation is deleted - Comprehensive tests for agent lifecycle management ### Discord RPC Cleanup (#129) - Removed file-based logging for Discord RPC - Replaced with proper `tracing` framework usage - Reduces disk usage and eliminates maintenance burden ### Close Modal Bug Fix (#128) - Fixed close confirmation modal not triggering after Discord RPC refactor - Removed frontend calls to deleted `log_discord_rpc` command - Modal now works correctly after all operations ### Responsive Design Fixes (#118) - Fixed top navigation icons getting cut off at small screen widths - Fixed Connect button disappearing on narrow screens - Fixed bottom status info (clock, CLI version) getting cut off - Added flex-wrap and mobile-optimised layouts - Icons-only mode on screens < 640px - Vertical stacking on screens < 768px ## Testing Improvements ๐งช ### Comprehensive Test Coverage (#114) - **417 backend tests** (up from 408) - **387 frontend tests** (up from 363) - **61%+ backend code coverage** - Added E2E integration tests for cross-platform notification commands - New test files: `agents.test.ts`, comprehensive CLI parsing tests - Tests for `debug_logger.rs`, `bridge_manager.rs`, `notifications.rs` - Console mocking for cleaner test output - Fixed flaky frontend tests ### Testing Documentation - Updated CLAUDE.md with comprehensive testing guidelines - Documented mocking approaches (console mocking, E2E command structure testing) - Added step-by-step guide for adding tests to new features - Goal to maintain ~100% test coverage documented ## Closes Closes #114 Closes #118 Closes #122 Closes #128 Closes #129 Closes #130 Closes #131 Closes #132 Closes #133 Closes #134 Closes #136 ## Technical Details - All new backend commands properly registered in `lib.rs` - CLI output parsing with comprehensive test coverage - Cross-platform compatibility verified through E2E tests (Linux CI can test Windows commands) - Theme-aware UI components using CSS variables throughout - Proper TypeScript types for all new stores and components - ESLint and Prettier compliant - All Clippy warnings addressed โจ This PR was created with help from Hikari~ ๐ธ Reviewed-on: #135 Co-authored-by: Hikari <hikari@nhcarrigan.com> Co-committed-by: Hikari <hikari@nhcarrigan.com>
158 lines
4.2 KiB
Rust
158 lines
4.2 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
use std::sync::Arc;
|
|
use tauri::{AppHandle, Emitter};
|
|
use tracing::{Level, Subscriber};
|
|
use tracing_subscriber::layer::{Context, Layer};
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct DebugLogEvent {
|
|
pub level: String,
|
|
pub message: String,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct TauriLogLayer {
|
|
app: Arc<AppHandle>,
|
|
}
|
|
|
|
impl TauriLogLayer {
|
|
pub fn new(app: AppHandle) -> Self {
|
|
Self {
|
|
app: Arc::new(app),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<S> Layer<S> for TauriLogLayer
|
|
where
|
|
S: Subscriber,
|
|
{
|
|
fn on_event(
|
|
&self,
|
|
event: &tracing::Event<'_>,
|
|
_ctx: Context<'_, S>,
|
|
) {
|
|
let metadata = event.metadata();
|
|
let level = match *metadata.level() {
|
|
Level::ERROR => "error",
|
|
Level::WARN => "warn",
|
|
Level::INFO => "info",
|
|
Level::DEBUG => "debug",
|
|
Level::TRACE => "debug",
|
|
};
|
|
|
|
// Extract message from the event
|
|
struct MessageVisitor {
|
|
message: String,
|
|
}
|
|
|
|
impl tracing::field::Visit for MessageVisitor {
|
|
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
|
|
if field.name() == "message" {
|
|
self.message = format!("{:?}", value);
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut visitor = MessageVisitor {
|
|
message: String::new(),
|
|
};
|
|
event.record(&mut visitor);
|
|
|
|
// If we couldn't extract a message, try to format the whole event
|
|
if visitor.message.is_empty() {
|
|
visitor.message = metadata.name().to_string();
|
|
}
|
|
|
|
// Strip quotes from the message
|
|
let message = visitor.message.trim_matches('"').to_string();
|
|
|
|
let log_event = DebugLogEvent {
|
|
level: level.to_string(),
|
|
message,
|
|
};
|
|
|
|
// Emit to frontend
|
|
let _ = self.app.emit("debug:log", log_event);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_debug_log_event_creation() {
|
|
let event = DebugLogEvent {
|
|
level: "info".to_string(),
|
|
message: "Test message".to_string(),
|
|
};
|
|
|
|
assert_eq!(event.level, "info");
|
|
assert_eq!(event.message, "Test message");
|
|
}
|
|
|
|
#[test]
|
|
fn test_debug_log_event_serialization() {
|
|
let event = DebugLogEvent {
|
|
level: "error".to_string(),
|
|
message: "Error occurred".to_string(),
|
|
};
|
|
|
|
let json = serde_json::to_string(&event).unwrap();
|
|
assert!(json.contains("\"level\":\"error\""));
|
|
assert!(json.contains("\"message\":\"Error occurred\""));
|
|
}
|
|
|
|
#[test]
|
|
fn test_debug_log_event_deserialization() {
|
|
let json = r#"{"level":"warn","message":"Warning message"}"#;
|
|
let event: DebugLogEvent = serde_json::from_str(json).unwrap();
|
|
|
|
assert_eq!(event.level, "warn");
|
|
assert_eq!(event.message, "Warning message");
|
|
}
|
|
|
|
#[test]
|
|
fn test_debug_log_event_with_special_characters() {
|
|
let event = DebugLogEvent {
|
|
level: "info".to_string(),
|
|
message: "Message with \"quotes\" and \n newlines".to_string(),
|
|
};
|
|
|
|
let json = serde_json::to_string(&event).unwrap();
|
|
let decoded: DebugLogEvent = serde_json::from_str(&json).unwrap();
|
|
|
|
assert_eq!(decoded.level, event.level);
|
|
assert_eq!(decoded.message, event.message);
|
|
}
|
|
|
|
#[test]
|
|
fn test_debug_log_event_with_unicode() {
|
|
let event = DebugLogEvent {
|
|
level: "debug".to_string(),
|
|
message: "Unicode: ๆฅๆฌ่ช ๐".to_string(),
|
|
};
|
|
|
|
let json = serde_json::to_string(&event).unwrap();
|
|
let decoded: DebugLogEvent = serde_json::from_str(&json).unwrap();
|
|
|
|
assert_eq!(decoded.message, "Unicode: ๆฅๆฌ่ช ๐");
|
|
}
|
|
|
|
#[test]
|
|
fn test_debug_log_event_all_levels() {
|
|
let levels = vec!["error", "warn", "info", "debug", "trace"];
|
|
|
|
for level in levels {
|
|
let event = DebugLogEvent {
|
|
level: level.to_string(),
|
|
message: format!("{} level message", level),
|
|
};
|
|
|
|
assert_eq!(event.level, level);
|
|
assert!(event.message.contains(level));
|
|
}
|
|
}
|
|
}
|