claude-mpm 4.2.1__py3-none-any.whl → 4.2.3__py3-none-any.whl

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 (57) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/templates/agent-manager.json +1 -1
  3. claude_mpm/agents/templates/agentic_coder_optimizer.json +1 -1
  4. claude_mpm/agents/templates/api_qa.json +1 -1
  5. claude_mpm/agents/templates/code_analyzer.json +1 -1
  6. claude_mpm/agents/templates/data_engineer.json +1 -1
  7. claude_mpm/agents/templates/documentation.json +1 -1
  8. claude_mpm/agents/templates/engineer.json +2 -2
  9. claude_mpm/agents/templates/gcp_ops_agent.json +14 -9
  10. claude_mpm/agents/templates/imagemagick.json +1 -1
  11. claude_mpm/agents/templates/memory_manager.json +1 -1
  12. claude_mpm/agents/templates/ops.json +1 -1
  13. claude_mpm/agents/templates/project_organizer.json +1 -1
  14. claude_mpm/agents/templates/qa.json +2 -2
  15. claude_mpm/agents/templates/refactoring_engineer.json +1 -1
  16. claude_mpm/agents/templates/research.json +3 -3
  17. claude_mpm/agents/templates/security.json +1 -1
  18. claude_mpm/agents/templates/test-non-mpm.json +20 -0
  19. claude_mpm/agents/templates/ticketing.json +1 -1
  20. claude_mpm/agents/templates/vercel_ops_agent.json +2 -2
  21. claude_mpm/agents/templates/version_control.json +1 -1
  22. claude_mpm/agents/templates/web_qa.json +3 -8
  23. claude_mpm/agents/templates/web_ui.json +1 -1
  24. claude_mpm/cli/commands/agents.py +3 -0
  25. claude_mpm/cli/commands/dashboard.py +3 -3
  26. claude_mpm/cli/commands/monitor.py +227 -64
  27. claude_mpm/core/config.py +25 -0
  28. claude_mpm/core/unified_agent_registry.py +2 -2
  29. claude_mpm/dashboard/static/css/code-tree.css +220 -1
  30. claude_mpm/dashboard/static/css/dashboard.css +286 -0
  31. claude_mpm/dashboard/static/dist/components/code-tree.js +1 -1
  32. claude_mpm/dashboard/static/js/components/code-simple.js +507 -15
  33. claude_mpm/dashboard/static/js/components/code-tree.js +2044 -124
  34. claude_mpm/dashboard/static/js/socket-client.js +5 -2
  35. claude_mpm/dashboard/templates/code_simple.html +79 -0
  36. claude_mpm/dashboard/templates/index.html +42 -41
  37. claude_mpm/services/agents/deployment/agent_deployment.py +4 -1
  38. claude_mpm/services/agents/deployment/agent_discovery_service.py +101 -2
  39. claude_mpm/services/agents/deployment/agent_format_converter.py +53 -9
  40. claude_mpm/services/agents/deployment/agent_template_builder.py +355 -25
  41. claude_mpm/services/agents/deployment/agent_validator.py +11 -6
  42. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +83 -15
  43. claude_mpm/services/agents/deployment/validation/template_validator.py +51 -40
  44. claude_mpm/services/cli/agent_listing_service.py +2 -2
  45. claude_mpm/services/dashboard/stable_server.py +389 -0
  46. claude_mpm/services/socketio/client_proxy.py +16 -0
  47. claude_mpm/services/socketio/dashboard_server.py +360 -0
  48. claude_mpm/services/socketio/handlers/code_analysis.py +27 -5
  49. claude_mpm/services/socketio/monitor_client.py +366 -0
  50. claude_mpm/services/socketio/monitor_server.py +505 -0
  51. claude_mpm/tools/code_tree_analyzer.py +95 -17
  52. {claude_mpm-4.2.1.dist-info → claude_mpm-4.2.3.dist-info}/METADATA +1 -1
  53. {claude_mpm-4.2.1.dist-info → claude_mpm-4.2.3.dist-info}/RECORD +57 -52
  54. {claude_mpm-4.2.1.dist-info → claude_mpm-4.2.3.dist-info}/WHEEL +0 -0
  55. {claude_mpm-4.2.1.dist-info → claude_mpm-4.2.3.dist-info}/entry_points.txt +0 -0
  56. {claude_mpm-4.2.1.dist-info → claude_mpm-4.2.3.dist-info}/licenses/LICENSE +0 -0
  57. {claude_mpm-4.2.1.dist-info → claude_mpm-4.2.3.dist-info}/top_level.txt +0 -0
@@ -1,31 +1,36 @@
1
1
  """
2
2
  Monitor command implementation for claude-mpm.
3
3
 
4
- WHY: This module provides CLI commands for managing the Socket.IO monitoring server,
5
- allowing users to start, stop, restart, and check status of the monitoring infrastructure.
6
- The monitor command now delegates to the unified dashboard service for consolidated operation.
4
+ WHY: This module provides CLI commands for managing the lightweight monitoring server,
5
+ allowing users to start, stop, restart, and check status of the independent monitoring service.
6
+ The monitor service runs as a stable background service on port 8766 (configurable).
7
7
 
8
8
  DESIGN DECISIONS:
9
- - Delegate to dashboard command for unified service architecture
10
- - Use BaseCommand for consistent CLI patterns
11
- - Maintain backward compatibility with existing Socket.IO server management
12
- - Support multiple output formats (json, yaml, table, text)
9
+ - Use independent MonitorServer for decoupled architecture
10
+ - Monitor runs on port 8766, Dashboard runs on port 8765
11
+ - Support background mode by default for stable always-on operation
12
+ - Provide status checking and port configuration
13
+ - Maintain minimal dependencies and resource usage
13
14
  """
14
15
 
16
+ import signal
17
+ import sys
18
+ import time
15
19
  from typing import Optional
16
20
 
17
21
  from ...constants import MonitorCommands
22
+ from ...services.port_manager import PortManager
23
+ from ...services.socketio.monitor_server import MonitorServer
18
24
  from ..shared import BaseCommand, CommandResult
19
- from .dashboard import DashboardCommand
20
25
 
21
26
 
22
27
  class MonitorCommand(BaseCommand):
23
- """Monitor command that delegates to the unified dashboard service."""
28
+ """Monitor command for managing the independent monitoring server."""
24
29
 
25
30
  def __init__(self):
26
31
  super().__init__("monitor")
27
- # Create dashboard command instance for delegation
28
- self.dashboard_command = DashboardCommand()
32
+ self.port_manager = PortManager()
33
+ self.server = None
29
34
 
30
35
  def validate_args(self, args) -> Optional[str]:
31
36
  """Validate command arguments."""
@@ -38,25 +43,25 @@ class MonitorCommand(BaseCommand):
38
43
  return None
39
44
 
40
45
  def run(self, args) -> CommandResult:
41
- """Execute the monitor command by delegating to dashboard service."""
46
+ """Execute the monitor command using independent MonitorServer."""
42
47
  try:
43
- self.logger.info("Monitor command delegating to unified dashboard service")
48
+ self.logger.info("Monitor command using independent monitoring server")
44
49
 
45
50
  # Handle default case (no subcommand) - default to status
46
51
  if not hasattr(args, "monitor_command") or not args.monitor_command:
47
- return self._status_dashboard(args)
52
+ return self._status_monitor(args)
48
53
 
49
- # Map monitor commands to dashboard commands
54
+ # Route to specific monitor commands
50
55
  if args.monitor_command == MonitorCommands.START.value:
51
- return self._start_dashboard(args)
56
+ return self._start_monitor(args)
52
57
  if args.monitor_command == MonitorCommands.STOP.value:
53
- return self._stop_dashboard(args)
58
+ return self._stop_monitor(args)
54
59
  if args.monitor_command == MonitorCommands.RESTART.value:
55
- return self._restart_dashboard(args)
60
+ return self._restart_monitor(args)
56
61
  if args.monitor_command == MonitorCommands.STATUS.value:
57
- return self._status_dashboard(args)
62
+ return self._status_monitor(args)
58
63
  if args.monitor_command == MonitorCommands.PORT.value:
59
- return self._start_dashboard_on_port(args)
64
+ return self._start_monitor_on_port(args)
60
65
 
61
66
  return CommandResult.error_result(
62
67
  f"Unknown monitor command: {args.monitor_command}"
@@ -66,66 +71,230 @@ class MonitorCommand(BaseCommand):
66
71
  self.logger.error(f"Error executing monitor command: {e}", exc_info=True)
67
72
  return CommandResult.error_result(f"Error executing monitor command: {e}")
68
73
 
69
- def _start_dashboard(self, args) -> CommandResult:
70
- """Start the dashboard service (unified HTTP + Socket.IO)."""
71
- self.logger.info("Starting unified dashboard service (HTTP + Socket.IO)")
72
- # Set default port to 8765 for unified service and background mode
73
- if not hasattr(args, "port") or args.port is None:
74
- args.port = 8765
75
- # Monitor command defaults to background mode for better UX
76
- if not hasattr(args, "background"):
77
- args.background = True
74
+ def _start_monitor(self, args) -> CommandResult:
75
+ """Start the monitor server."""
76
+ port = getattr(args, "port", 8765) # Default to 8765 for monitor
77
+ host = getattr(args, "host", "localhost")
78
+ background = getattr(
79
+ args, "background", True
80
+ ) # Default to background for monitor
81
+
82
+ self.logger.info(
83
+ f"Starting monitor server on {host}:{port} (background: {background})"
84
+ )
78
85
 
79
- return self.dashboard_command._start_dashboard(args)
86
+ # Check if monitor is already running
87
+ if self._is_monitor_running(port):
88
+ return CommandResult.success_result(
89
+ f"Monitor server already running on {host}:{port}",
90
+ data={"url": f"http://{host}:{port}", "port": port},
91
+ )
80
92
 
81
- def _stop_dashboard(self, args) -> CommandResult:
82
- """Stop the dashboard service."""
83
- self.logger.info("Stopping unified dashboard service")
84
- # Use default port if not specified
85
- if not hasattr(args, "port") or args.port is None:
86
- args.port = 8765
93
+ if background:
94
+ # Start monitor server in background
95
+ try:
96
+ self.server = MonitorServer(host=host, port=port)
97
+ if self.server.start_sync():
98
+ return CommandResult.success_result(
99
+ f"Monitor server started on {host}:{port}",
100
+ data={"url": f"http://{host}:{port}", "port": port},
101
+ )
102
+ return CommandResult.error_result("Failed to start monitor server")
103
+ except Exception as e:
104
+ return CommandResult.error_result(
105
+ f"Failed to start monitor server: {e}"
106
+ )
107
+ else:
108
+ # Run monitor in foreground mode
109
+ try:
110
+ print(f"Starting monitor server on {host}:{port}...")
111
+ print("Press Ctrl+C to stop the server")
112
+
113
+ self.server = MonitorServer(host=host, port=port)
114
+
115
+ # Set up signal handlers for graceful shutdown
116
+ def signal_handler(signum, frame):
117
+ print("\nShutting down monitor server...")
118
+ if self.server:
119
+ self.server.stop_sync()
120
+ sys.exit(0)
121
+
122
+ signal.signal(signal.SIGINT, signal_handler)
123
+ signal.signal(signal.SIGTERM, signal_handler)
124
+
125
+ # Start the server
126
+ if not self.server.start_sync():
127
+ return CommandResult.error_result("Failed to start monitor server")
128
+
129
+ # Keep the main thread alive while server is running
130
+ try:
131
+ while self.server.is_running():
132
+ time.sleep(1)
133
+ except KeyboardInterrupt:
134
+ pass
135
+
136
+ # Stop the server
137
+ if self.server:
138
+ self.server.stop_sync()
139
+
140
+ return CommandResult.success_result("Monitor server stopped")
141
+
142
+ except KeyboardInterrupt:
143
+ print("\nMonitor server stopped by user")
144
+ return CommandResult.success_result("Monitor server stopped")
145
+ except Exception as e:
146
+ return CommandResult.error_result(
147
+ f"Failed to start monitor server: {e}"
148
+ )
149
+
150
+ def _stop_monitor(self, args) -> CommandResult:
151
+ """Stop the monitor server."""
152
+ port = getattr(args, "port", 8766)
153
+
154
+ self.logger.info(f"Stopping monitor server on port {port}")
155
+
156
+ if not self._is_monitor_running(port):
157
+ return CommandResult.success_result(
158
+ f"No monitor server running on port {port}"
159
+ )
87
160
 
88
- return self.dashboard_command._stop_dashboard(args)
161
+ # Try to stop our server instance if we have one
162
+ if self.server and self.server.is_running():
163
+ try:
164
+ self.server.stop_sync()
165
+ return CommandResult.success_result(
166
+ f"Monitor server stopped on port {port}"
167
+ )
168
+ except Exception as e:
169
+ return CommandResult.error_result(f"Error stopping monitor server: {e}")
170
+
171
+ # If we don't have a server instance, try port manager cleanup
172
+ try:
173
+ self.port_manager.cleanup_dead_instances()
174
+ active_instances = self.port_manager.list_active_instances()
175
+
176
+ # Look for instances on the target port
177
+ for instance in active_instances:
178
+ if (
179
+ instance.get("port") == port
180
+ and instance.get("service_type") == "monitor"
181
+ ):
182
+ # Found an instance, but we can't stop it directly
183
+ # This would need to be implemented with a proper process manager
184
+ return CommandResult.error_result(
185
+ f"Monitor server found on port {port} but cannot be stopped "
186
+ "(no direct control - you may need to kill the process manually)"
187
+ )
188
+
189
+ return CommandResult.success_result(
190
+ f"No monitor server found on port {port}"
191
+ )
89
192
 
90
- def _restart_dashboard(self, args) -> CommandResult:
91
- """Restart the dashboard service."""
92
- self.logger.info("Restarting unified dashboard service")
193
+ except Exception as e:
194
+ return CommandResult.error_result(
195
+ f"Error checking monitor server status: {e}"
196
+ )
197
+
198
+ def _restart_monitor(self, args) -> CommandResult:
199
+ """Restart the monitor server."""
200
+ self.logger.info("Restarting monitor server")
93
201
 
94
202
  # Stop first
95
- stop_result = self._stop_dashboard(args)
203
+ stop_result = self._stop_monitor(args)
96
204
  if not stop_result.success:
97
- self.logger.warning("Failed to stop service for restart, proceeding anyway")
205
+ self.logger.warning(
206
+ "Failed to stop monitor server for restart, proceeding anyway"
207
+ )
98
208
 
99
209
  # Wait a moment
100
- import time
101
-
102
- time.sleep(1)
210
+ time.sleep(2)
103
211
 
104
212
  # Start again
105
- return self._start_dashboard(args)
213
+ return self._start_monitor(args)
214
+
215
+ def _status_monitor(self, args) -> CommandResult:
216
+ """Get monitor server status."""
217
+ port = getattr(args, "port", 8766)
218
+ verbose = getattr(args, "verbose", False)
219
+ show_ports = getattr(args, "show_ports", False)
220
+
221
+ # Check if monitor is running
222
+ monitor_running = self._is_monitor_running(port)
223
+
224
+ status_data = {
225
+ "running": monitor_running,
226
+ "default_port": port,
227
+ "service_type": "monitor",
228
+ }
229
+
230
+ if monitor_running:
231
+ status_data["url"] = f"http://localhost:{port}"
232
+
233
+ # Check all ports if requested
234
+ if show_ports:
235
+ port_status = {}
236
+ for check_port in range(8766, 8776): # Monitor port range
237
+ is_running = self._is_monitor_running(check_port)
238
+ port_status[check_port] = {
239
+ "running": is_running,
240
+ "url": f"http://localhost:{check_port}" if is_running else None,
241
+ }
242
+ status_data["ports"] = port_status
243
+
244
+ # Get active instances from port manager
245
+ self.port_manager.cleanup_dead_instances()
246
+ active_instances = self.port_manager.list_active_instances()
247
+ if active_instances:
248
+ monitor_instances = [
249
+ inst
250
+ for inst in active_instances
251
+ if inst.get("service_type") == "monitor"
252
+ ]
253
+ if monitor_instances:
254
+ status_data["active_instances"] = monitor_instances
255
+
256
+ if verbose and self.server:
257
+ status_data["server_stats"] = self.server.get_stats()
258
+
259
+ # Format output message
260
+ if monitor_running:
261
+ message = f"Monitor server is running at {status_data['url']}"
262
+ else:
263
+ message = "Monitor server is not running"
264
+
265
+ return CommandResult.success_result(message, data=status_data)
266
+
267
+ def _start_monitor_on_port(self, args) -> CommandResult:
268
+ """Start monitor server on specific port."""
269
+ port = getattr(args, "port", 8766)
270
+ self.logger.info(f"Starting monitor server on port {port}")
106
271
 
107
- def _status_dashboard(self, args) -> CommandResult:
108
- """Get dashboard service status."""
109
- return self.dashboard_command._status_dashboard(args)
110
-
111
- def _start_dashboard_on_port(self, args) -> CommandResult:
112
- """Start dashboard service on specific port."""
113
- self.logger.info(
114
- f"Starting dashboard service on port {getattr(args, 'port', 8765)}"
115
- )
116
272
  # Ensure background mode for port-specific starts
117
273
  if not hasattr(args, "background"):
118
274
  args.background = True
119
275
 
120
- return self.dashboard_command._start_dashboard(args)
276
+ return self._start_monitor(args)
277
+
278
+ def _is_monitor_running(self, port: int) -> bool:
279
+ """Check if monitor server is running on given port."""
280
+ import socket
281
+
282
+ try:
283
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
284
+ s.settimeout(1)
285
+ result = s.connect_ex(("localhost", port))
286
+ return result == 0
287
+ except Exception:
288
+ return False
121
289
 
122
290
 
123
291
  def manage_monitor(args):
124
292
  """
125
293
  Main entry point for monitor command.
126
294
 
127
- The monitor command now delegates to the unified dashboard service for consolidated operation.
128
- Both dashboard and monitor commands now use the same underlying service on port 8765.
295
+ The monitor command manages an independent lightweight monitoring server on port 8766.
296
+ This server runs separately from the dashboard (port 8765) and provides stable
297
+ event collection and relay services.
129
298
  """
130
299
  command = MonitorCommand()
131
300
  error = command.validate_args(args)
@@ -148,9 +317,3 @@ def manage_monitor(args):
148
317
  if result.message:
149
318
  print(f"Error: {result.message}")
150
319
  return 1
151
-
152
-
153
- # All legacy functions have been removed.
154
- # The monitor command now delegates to the unified dashboard service.
155
- # This consolidation provides a single service that handles both HTTP (port 8765)
156
- # and Socket.IO (also on port 8765) rather than separate services on different ports.
claude_mpm/core/config.py CHANGED
@@ -527,11 +527,36 @@ class Config:
527
527
  },
528
528
  },
529
529
  },
530
+ # Monitor server configuration (decoupled from dashboard)
531
+ "monitor_server": {
532
+ "host": "localhost",
533
+ "port": 8765, # Default monitor port (shared with dashboard)
534
+ "enable_health_monitoring": True,
535
+ "auto_start": False, # Don't auto-start with dashboard by default
536
+ "event_buffer_size": 2000, # Larger buffer for monitor server
537
+ "client_timeout": 60, # Timeout for inactive clients
538
+ },
539
+ # Dashboard server configuration (connects to monitor)
540
+ "dashboard_server": {
541
+ "host": "localhost",
542
+ "port": 8765, # Dashboard UI port
543
+ "monitor_host": "localhost", # Monitor server host to connect to
544
+ "monitor_port": 8765, # Monitor server port to connect to
545
+ "auto_connect_monitor": True, # Automatically connect to monitor
546
+ "monitor_reconnect": True, # Auto-reconnect to monitor if disconnected
547
+ "fallback_standalone": True, # Run in standalone mode if monitor unavailable
548
+ },
530
549
  # Agent deployment configuration
531
550
  "agent_deployment": {
532
551
  "excluded_agents": [], # List of agent IDs to exclude from deployment
533
552
  "exclude_dependencies": False, # Whether to exclude agent dependencies too
534
553
  "case_sensitive": False, # Whether agent name matching is case-sensitive
554
+ "filter_non_mpm_agents": True, # Filter out non-MPM agents by default
555
+ "mpm_author_patterns": [
556
+ "claude mpm",
557
+ "claude-mpm",
558
+ "anthropic",
559
+ ], # Patterns for MPM agents
535
560
  },
536
561
  # Instruction reinforcement system configuration
537
562
  "instruction_reinforcement": {
@@ -420,8 +420,8 @@ class UnifiedAgentRegistry:
420
420
  for _i, line in enumerate(lines[1:], 1):
421
421
  if line.strip() == "---":
422
422
  break
423
- if line.startswith("description:"):
424
- return line.split(":", 1)[1].strip().strip("\"'")
423
+ if line.strip().startswith("description:"):
424
+ return line.strip().split(":", 1)[1].strip().strip("\"'")
425
425
 
426
426
  # Look for first paragraph
427
427
  for line in lines:
@@ -459,7 +459,7 @@
459
459
  border-radius: 8px;
460
460
  padding: 20px;
461
461
  position: relative;
462
- overflow: auto;
462
+ overflow: hidden;
463
463
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
464
464
  border-left: 1px solid #e2e8f0;
465
465
  }
@@ -828,6 +828,8 @@
828
828
  .code-node text {
829
829
  font: 12px sans-serif;
830
830
  pointer-events: none;
831
+ /* Smooth transitions for zoom-based text scaling */
832
+ transition: font-size 0.2s ease;
831
833
  }
832
834
 
833
835
  .code-node.module circle {
@@ -974,6 +976,8 @@
974
976
  text-anchor: middle;
975
977
  dominant-baseline: central;
976
978
  font-weight: 500;
979
+ /* Smooth transitions for zoom-based scaling */
980
+ transition: font-size 0.2s ease;
977
981
  }
978
982
 
979
983
  .code-node.directory:hover .item-count-badge {
@@ -1405,4 +1409,219 @@
1405
1409
  transform: translateX(0);
1406
1410
  opacity: 1;
1407
1411
  }
1412
+ }
1413
+
1414
+ /* Back Button for Focused Directory View */
1415
+ .tree-control-btn.back-btn {
1416
+ background: #3182ce !important;
1417
+ color: white !important;
1418
+ border: 1px solid #2c5aa0 !important;
1419
+ font-weight: 600;
1420
+ margin-right: 10px;
1421
+ }
1422
+
1423
+ .tree-control-btn.back-btn:hover {
1424
+ background: #2c5aa0 !important;
1425
+ transform: translateY(-1px);
1426
+ box-shadow: 0 4px 8px rgba(49, 130, 206, 0.3);
1427
+ }
1428
+
1429
+ /* Focused Directory View Styling */
1430
+ .code-tree-container.focused {
1431
+ border: 2px solid #3182ce;
1432
+ border-radius: 8px;
1433
+ background: linear-gradient(135deg, #f7fafc 0%, #edf2f7 100%);
1434
+ }
1435
+
1436
+ .code-tree-container.focused .tree-controls-toolbar {
1437
+ background: #e6fffa;
1438
+ border-bottom: 1px solid #81e6d9;
1439
+ }
1440
+
1441
+ /* Corner Controls Styling */
1442
+ .tree-corner-controls {
1443
+ position: absolute;
1444
+ z-index: 1000;
1445
+ background: rgba(255, 255, 255, 0.95);
1446
+ border: 1px solid #e2e8f0;
1447
+ border-radius: 6px;
1448
+ padding: 8px 12px;
1449
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
1450
+ backdrop-filter: blur(4px);
1451
+ font-size: 12px;
1452
+ }
1453
+
1454
+ .tree-corner-controls.top-left {
1455
+ top: 10px;
1456
+ left: 10px;
1457
+ }
1458
+
1459
+ .tree-corner-controls.top-right {
1460
+ top: 10px;
1461
+ right: 10px;
1462
+ }
1463
+
1464
+ .tree-corner-controls.bottom-left {
1465
+ bottom: 10px;
1466
+ left: 10px;
1467
+ display: flex;
1468
+ flex-direction: column;
1469
+ align-items: flex-start;
1470
+ gap: 4px;
1471
+ }
1472
+
1473
+ .tree-corner-controls.bottom-right {
1474
+ bottom: 10px;
1475
+ right: 10px;
1476
+ }
1477
+
1478
+ .tree-corner-controls .control-group {
1479
+ display: flex;
1480
+ align-items: center;
1481
+ gap: 8px;
1482
+ flex-wrap: wrap;
1483
+ }
1484
+
1485
+ .tree-corner-controls .control-label {
1486
+ font-weight: 600;
1487
+ color: #4a5568;
1488
+ white-space: nowrap;
1489
+ }
1490
+
1491
+ .tree-corner-controls .checkbox-group {
1492
+ display: flex;
1493
+ gap: 12px;
1494
+ flex-wrap: wrap;
1495
+ }
1496
+
1497
+ .tree-corner-controls .checkbox-label {
1498
+ display: flex;
1499
+ align-items: center;
1500
+ gap: 4px;
1501
+ font-size: 11px;
1502
+ color: #4a5568;
1503
+ cursor: pointer;
1504
+ white-space: nowrap;
1505
+ }
1506
+
1507
+ .tree-corner-controls .checkbox-label input[type="checkbox"] {
1508
+ margin: 0;
1509
+ transform: scale(0.9);
1510
+ }
1511
+
1512
+ .tree-corner-controls .select-compact,
1513
+ .tree-corner-controls .search-compact,
1514
+ .tree-corner-controls .input-compact {
1515
+ font-size: 11px;
1516
+ padding: 4px 8px;
1517
+ border: 1px solid #d1d5db;
1518
+ border-radius: 4px;
1519
+ background: white;
1520
+ }
1521
+
1522
+ .tree-corner-controls .search-compact {
1523
+ width: 120px;
1524
+ }
1525
+
1526
+ .tree-corner-controls .input-compact {
1527
+ width: 140px;
1528
+ }
1529
+
1530
+ .tree-corner-controls .stats-display {
1531
+ font-size: 11px;
1532
+ color: #6b7280;
1533
+ font-weight: 500;
1534
+ margin-bottom: 4px;
1535
+ }
1536
+
1537
+ .tree-corner-controls .status-display {
1538
+ font-size: 10px;
1539
+ color: #4b5563;
1540
+ font-style: italic;
1541
+ opacity: 0.8;
1542
+ max-width: 200px;
1543
+ overflow: hidden;
1544
+ text-overflow: ellipsis;
1545
+ white-space: nowrap;
1546
+ }
1547
+
1548
+ .tree-corner-controls .status-display .breadcrumb-ticker {
1549
+ display: block;
1550
+ }
1551
+
1552
+ .tree-corner-controls .status-display #breadcrumb-content {
1553
+ display: inline-block;
1554
+ animation: none; /* Disable any existing animations for corner display */
1555
+ }
1556
+
1557
+ /* Hide corner controls when in focused mode to avoid clutter */
1558
+ .code-tree-container.focused .tree-corner-controls {
1559
+ opacity: 0.7;
1560
+ }
1561
+
1562
+ .code-tree-container.focused .tree-corner-controls:hover {
1563
+ opacity: 1;
1564
+ }
1565
+
1566
+ /* Force horizontal text for specific nodes */
1567
+ .node-label.horizontal-text {
1568
+ writing-mode: horizontal-tb !important;
1569
+ text-orientation: mixed !important;
1570
+ transform: rotate(0deg) !important;
1571
+ }
1572
+
1573
+ /* Zoom controls styling */
1574
+ .zoom-level-display {
1575
+ display: inline-block;
1576
+ padding: 2px 6px;
1577
+ background: rgba(0, 0, 0, 0.05);
1578
+ border-radius: 3px;
1579
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
1580
+ font-size: 11px !important;
1581
+ color: #718096 !important;
1582
+ margin-left: 8px !important;
1583
+ vertical-align: middle;
1584
+ min-width: 35px;
1585
+ text-align: center;
1586
+ }
1587
+
1588
+ /* Enhanced tree control buttons for zoom */
1589
+ .tree-control-btn {
1590
+ background: #f8f9fa;
1591
+ border: 1px solid #e2e8f0;
1592
+ border-radius: 4px;
1593
+ padding: 6px 8px;
1594
+ margin: 0 2px;
1595
+ cursor: pointer;
1596
+ font-size: 12px;
1597
+ color: #4a5568;
1598
+ transition: all 0.2s;
1599
+ display: inline-block;
1600
+ vertical-align: middle;
1601
+ }
1602
+
1603
+ .tree-control-btn:hover {
1604
+ background: #e2e8f0;
1605
+ border-color: #cbd5e0;
1606
+ color: #2d3748;
1607
+ }
1608
+
1609
+ .tree-control-btn:active {
1610
+ background: #cbd5e0;
1611
+ transform: translateY(1px);
1612
+ }
1613
+
1614
+ /* Zoom-specific button styling */
1615
+ .tree-control-btn[title*="Zoom"] {
1616
+ font-family: system-ui, -apple-system, sans-serif;
1617
+ font-weight: 500;
1618
+ }
1619
+
1620
+ /* Pan cursor when dragging */
1621
+ .code-tree-container svg {
1622
+ cursor: grab;
1623
+ }
1624
+
1625
+ .code-tree-container svg:active {
1626
+ cursor: grabbing;
1408
1627
  }