claude-mpm 4.1.8__py3-none-any.whl → 4.1.11__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 (111) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/INSTRUCTIONS.md +26 -1
  3. claude_mpm/agents/agents_metadata.py +57 -0
  4. claude_mpm/agents/templates/.claude-mpm/memories/README.md +17 -0
  5. claude_mpm/agents/templates/.claude-mpm/memories/engineer_memories.md +3 -0
  6. claude_mpm/agents/templates/agent-manager.json +263 -17
  7. claude_mpm/agents/templates/agentic_coder_optimizer.json +222 -0
  8. claude_mpm/agents/templates/code_analyzer.json +18 -8
  9. claude_mpm/agents/templates/engineer.json +1 -1
  10. claude_mpm/agents/templates/logs/prompts/agent_engineer_20250826_014258_728.md +39 -0
  11. claude_mpm/agents/templates/qa.json +1 -1
  12. claude_mpm/agents/templates/research.json +1 -1
  13. claude_mpm/cli/__init__.py +15 -0
  14. claude_mpm/cli/commands/__init__.py +6 -0
  15. claude_mpm/cli/commands/analyze.py +548 -0
  16. claude_mpm/cli/commands/analyze_code.py +524 -0
  17. claude_mpm/cli/commands/configure.py +78 -28
  18. claude_mpm/cli/commands/configure_tui.py +62 -60
  19. claude_mpm/cli/commands/dashboard.py +288 -0
  20. claude_mpm/cli/commands/debug.py +1386 -0
  21. claude_mpm/cli/commands/mpm_init.py +427 -0
  22. claude_mpm/cli/commands/mpm_init_handler.py +83 -0
  23. claude_mpm/cli/parsers/analyze_code_parser.py +170 -0
  24. claude_mpm/cli/parsers/analyze_parser.py +135 -0
  25. claude_mpm/cli/parsers/base_parser.py +44 -0
  26. claude_mpm/cli/parsers/dashboard_parser.py +113 -0
  27. claude_mpm/cli/parsers/debug_parser.py +319 -0
  28. claude_mpm/cli/parsers/mpm_init_parser.py +122 -0
  29. claude_mpm/constants.py +13 -1
  30. claude_mpm/core/framework_loader.py +148 -6
  31. claude_mpm/core/log_manager.py +16 -13
  32. claude_mpm/core/logger.py +1 -1
  33. claude_mpm/core/unified_agent_registry.py +1 -1
  34. claude_mpm/dashboard/.claude-mpm/socketio-instances.json +1 -0
  35. claude_mpm/dashboard/analysis_runner.py +455 -0
  36. claude_mpm/dashboard/static/built/components/activity-tree.js +2 -0
  37. claude_mpm/dashboard/static/built/components/agent-inference.js +1 -1
  38. claude_mpm/dashboard/static/built/components/code-tree.js +2 -0
  39. claude_mpm/dashboard/static/built/components/code-viewer.js +2 -0
  40. claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
  41. claude_mpm/dashboard/static/built/components/file-tool-tracker.js +1 -1
  42. claude_mpm/dashboard/static/built/components/module-viewer.js +1 -1
  43. claude_mpm/dashboard/static/built/components/session-manager.js +1 -1
  44. claude_mpm/dashboard/static/built/components/working-directory.js +1 -1
  45. claude_mpm/dashboard/static/built/dashboard.js +1 -1
  46. claude_mpm/dashboard/static/built/socket-client.js +1 -1
  47. claude_mpm/dashboard/static/css/activity.css +549 -0
  48. claude_mpm/dashboard/static/css/code-tree.css +1175 -0
  49. claude_mpm/dashboard/static/css/dashboard.css +245 -0
  50. claude_mpm/dashboard/static/dist/components/activity-tree.js +2 -0
  51. claude_mpm/dashboard/static/dist/components/code-tree.js +2 -0
  52. claude_mpm/dashboard/static/dist/components/code-viewer.js +2 -0
  53. claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
  54. claude_mpm/dashboard/static/dist/components/session-manager.js +1 -1
  55. claude_mpm/dashboard/static/dist/components/working-directory.js +1 -1
  56. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  57. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  58. claude_mpm/dashboard/static/js/components/activity-tree.js +1338 -0
  59. claude_mpm/dashboard/static/js/components/code-tree.js +2535 -0
  60. claude_mpm/dashboard/static/js/components/code-viewer.js +480 -0
  61. claude_mpm/dashboard/static/js/components/event-viewer.js +59 -9
  62. claude_mpm/dashboard/static/js/components/session-manager.js +40 -4
  63. claude_mpm/dashboard/static/js/components/socket-manager.js +12 -0
  64. claude_mpm/dashboard/static/js/components/ui-state-manager.js +4 -0
  65. claude_mpm/dashboard/static/js/components/working-directory.js +17 -1
  66. claude_mpm/dashboard/static/js/dashboard.js +51 -0
  67. claude_mpm/dashboard/static/js/socket-client.js +465 -29
  68. claude_mpm/dashboard/templates/index.html +182 -4
  69. claude_mpm/hooks/claude_hooks/hook_handler.py +182 -5
  70. claude_mpm/hooks/claude_hooks/installer.py +386 -113
  71. claude_mpm/scripts/claude-hook-handler.sh +161 -0
  72. claude_mpm/scripts/socketio_daemon.py +121 -8
  73. claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +2 -2
  74. claude_mpm/services/agents/deployment/agent_record_service.py +1 -2
  75. claude_mpm/services/agents/memory/memory_format_service.py +1 -3
  76. claude_mpm/services/cli/agent_cleanup_service.py +1 -5
  77. claude_mpm/services/cli/agent_dependency_service.py +1 -1
  78. claude_mpm/services/cli/agent_validation_service.py +3 -4
  79. claude_mpm/services/cli/dashboard_launcher.py +2 -3
  80. claude_mpm/services/cli/startup_checker.py +0 -11
  81. claude_mpm/services/core/cache_manager.py +1 -3
  82. claude_mpm/services/core/path_resolver.py +1 -4
  83. claude_mpm/services/core/service_container.py +2 -2
  84. claude_mpm/services/diagnostics/checks/instructions_check.py +1 -2
  85. claude_mpm/services/infrastructure/monitoring/__init__.py +11 -11
  86. claude_mpm/services/infrastructure/monitoring.py +11 -11
  87. claude_mpm/services/project/architecture_analyzer.py +1 -1
  88. claude_mpm/services/project/dependency_analyzer.py +4 -4
  89. claude_mpm/services/project/language_analyzer.py +3 -3
  90. claude_mpm/services/project/metrics_collector.py +3 -6
  91. claude_mpm/services/socketio/event_normalizer.py +64 -0
  92. claude_mpm/services/socketio/handlers/__init__.py +2 -0
  93. claude_mpm/services/socketio/handlers/code_analysis.py +672 -0
  94. claude_mpm/services/socketio/handlers/registry.py +2 -0
  95. claude_mpm/services/socketio/server/connection_manager.py +6 -4
  96. claude_mpm/services/socketio/server/core.py +100 -11
  97. claude_mpm/services/socketio/server/main.py +8 -2
  98. claude_mpm/services/visualization/__init__.py +19 -0
  99. claude_mpm/services/visualization/mermaid_generator.py +938 -0
  100. claude_mpm/tools/__main__.py +208 -0
  101. claude_mpm/tools/code_tree_analyzer.py +1596 -0
  102. claude_mpm/tools/code_tree_builder.py +631 -0
  103. claude_mpm/tools/code_tree_events.py +416 -0
  104. claude_mpm/tools/socketio_debug.py +671 -0
  105. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/METADATA +2 -1
  106. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/RECORD +110 -74
  107. claude_mpm/agents/schema/agent_schema.json +0 -314
  108. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/WHEEL +0 -0
  109. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/entry_points.txt +0 -0
  110. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/licenses/LICENSE +0 -0
  111. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1338 @@
1
+ /**
2
+ * Activity Tree Component
3
+ *
4
+ * D3.js-based collapsible tree visualization for showing PM activity hierarchy.
5
+ * Displays PM actions, TodoWrite delegations, agent assignments, and tool usage.
6
+ */
7
+
8
+ class ActivityTree {
9
+ constructor() {
10
+ this.container = null;
11
+ this.svg = null;
12
+ this.treeData = null;
13
+ this.root = null;
14
+ this.treeLayout = null;
15
+ this.treeGroup = null;
16
+ this.events = [];
17
+ this.todoWriteStack = [];
18
+ this.activeAgent = null;
19
+ this.activeAgentStack = [];
20
+ this.margin = {top: 20, right: 120, bottom: 20, left: 120};
21
+ this.width = 960 - this.margin.left - this.margin.right;
22
+ this.height = 500 - this.margin.top - this.margin.bottom;
23
+ this.nodeId = 0;
24
+ this.duration = 750;
25
+ this.timeRange = '30min';
26
+ this.searchTerm = '';
27
+ this.tooltip = null;
28
+ this.initialized = false;
29
+ }
30
+
31
+ /**
32
+ * Initialize the activity tree visualization
33
+ */
34
+ initialize() {
35
+ console.log('ActivityTree.initialize() called, initialized:', this.initialized);
36
+
37
+ // Check if already initialized
38
+ if (this.initialized) {
39
+ console.log('Activity tree already initialized, skipping');
40
+ return;
41
+ }
42
+
43
+ // First try to find the container
44
+ this.container = document.getElementById('activity-tree-container');
45
+ if (!this.container) {
46
+ // Fall back to the inner div if container not found
47
+ this.container = document.getElementById('activity-tree');
48
+ if (!this.container) {
49
+ console.error('Activity tree container not found in DOM');
50
+ return;
51
+ }
52
+ }
53
+
54
+ // Clear any existing text content that might be in the container
55
+ if (this.container.textContent && this.container.textContent.trim()) {
56
+ console.log('Clearing existing text content from container:', this.container.textContent);
57
+ this.container.textContent = '';
58
+ }
59
+
60
+ console.log('Activity tree container found:', this.container);
61
+
62
+ // Check if the container is visible before initializing
63
+ const tabPanel = document.getElementById('activity-tab');
64
+ if (!tabPanel) {
65
+ console.error('Activity tab panel (#activity-tab) not found in DOM');
66
+ return;
67
+ }
68
+
69
+ // Initialize even if tab is not active, but don't render until visible
70
+ if (!tabPanel.classList.contains('active')) {
71
+ console.log('Activity tab not active, initializing but deferring render');
72
+ // Clear any text content that might be showing
73
+ if (this.container.textContent && this.container.textContent.trim()) {
74
+ this.container.textContent = '';
75
+ }
76
+ // Set up basic structure but defer visualization
77
+ this.setupControls();
78
+ this.initializeTreeData();
79
+ this.subscribeToEvents();
80
+ this.initialized = true;
81
+ return;
82
+ }
83
+
84
+ // Clear container before creating visualization
85
+ if (this.container.textContent && this.container.textContent.trim()) {
86
+ console.log('Clearing container text before creating visualization');
87
+ this.container.textContent = '';
88
+ }
89
+
90
+ this.setupControls();
91
+ this.createVisualization();
92
+
93
+ if (!this.svg || !this.treeGroup) {
94
+ console.error('Failed to create D3 visualization elements');
95
+ // Show error message in container
96
+ if (this.container) {
97
+ this.container.innerHTML = '<div style="padding: 20px; text-align: center; color: #e53e3e;">⚠️ Failed to create visualization. Please refresh the page.</div>';
98
+ }
99
+ return;
100
+ }
101
+
102
+ this.initializeTreeData();
103
+
104
+ // Only update if we have a valid root
105
+ if (this.root) {
106
+ this.update(this.root);
107
+ } else {
108
+ console.warn('Root not created, skipping initial update');
109
+ }
110
+
111
+ this.subscribeToEvents();
112
+
113
+ this.initialized = true;
114
+ console.log('Activity tree initialization complete');
115
+ }
116
+
117
+ /**
118
+ * Force show the tree visualization
119
+ */
120
+ forceShow() {
121
+ console.log('ActivityTree.forceShow() called');
122
+
123
+ // Ensure container is available
124
+ if (!this.container) {
125
+ this.container = document.getElementById('activity-tree-container') || document.getElementById('activity-tree');
126
+ if (!this.container) {
127
+ console.error('Cannot find activity tree container');
128
+ return;
129
+ }
130
+ }
131
+
132
+ // Clear any text content
133
+ if (this.container.textContent && this.container.textContent.trim()) {
134
+ console.log('Clearing text from container:', this.container.textContent);
135
+ this.container.innerHTML = '';
136
+ }
137
+
138
+ // Create visualization if needed
139
+ if (!this.svg) {
140
+ this.createVisualization();
141
+ }
142
+
143
+ // Initialize tree data if needed
144
+ if (!this.root) {
145
+ this.initializeTreeData();
146
+ }
147
+
148
+ // Update the tree
149
+ if (this.root && this.svg && this.treeGroup) {
150
+ this.update(this.root);
151
+ }
152
+
153
+ // Ensure the SVG is visible
154
+ if (this.svg) {
155
+ const svgNode = this.svg.node();
156
+ if (svgNode) {
157
+ svgNode.style.display = 'block';
158
+ svgNode.style.visibility = 'visible';
159
+ }
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Render the visualization when tab becomes visible (called when switching to Activity tab)
165
+ */
166
+ renderWhenVisible() {
167
+ console.log('ActivityTree.renderWhenVisible() called');
168
+
169
+ // Ensure the container is clean
170
+ if (this.container && this.container.textContent && this.container.textContent.trim() && !this.svg) {
171
+ console.log('Clearing text content before rendering:', this.container.textContent);
172
+ this.container.textContent = '';
173
+ }
174
+
175
+ if (!this.initialized) {
176
+ console.log('Not initialized yet, calling initialize...');
177
+ this.initialize();
178
+ return;
179
+ }
180
+
181
+ // If already initialized but no visualization, create it
182
+ if (!this.svg) {
183
+ console.log('Creating deferred visualization...');
184
+ this.createVisualization();
185
+ if (this.svg && this.treeGroup && this.root) {
186
+ this.update(this.root);
187
+ } else if (!this.root) {
188
+ console.warn('No root node available, initializing tree data...');
189
+ this.initializeTreeData();
190
+ if (this.root && this.svg && this.treeGroup) {
191
+ this.update(this.root);
192
+ }
193
+ }
194
+ }
195
+
196
+ // Force update to ensure tree is rendered with current data
197
+ if (this.root && this.svg) {
198
+ console.log('Updating tree with current data...');
199
+ this.update(this.root);
200
+ } else {
201
+ console.warn('Cannot update tree - missing components:', {
202
+ hasRoot: !!this.root,
203
+ hasSvg: !!this.svg,
204
+ hasTreeGroup: !!this.treeGroup
205
+ });
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Setup control handlers
211
+ */
212
+ setupControls() {
213
+ // Expand all button
214
+ const expandAllBtn = document.getElementById('expand-all');
215
+ if (expandAllBtn) {
216
+ expandAllBtn.addEventListener('click', () => this.expandAll());
217
+ }
218
+
219
+ // Collapse all button
220
+ const collapseAllBtn = document.getElementById('collapse-all');
221
+ if (collapseAllBtn) {
222
+ collapseAllBtn.addEventListener('click', () => this.collapseAll());
223
+ }
224
+
225
+ // Reset zoom button
226
+ const resetZoomBtn = document.getElementById('reset-zoom');
227
+ if (resetZoomBtn) {
228
+ resetZoomBtn.addEventListener('click', () => this.resetZoom());
229
+ }
230
+
231
+ // Time range selector
232
+ const timeRangeSelect = document.getElementById('time-range');
233
+ if (timeRangeSelect) {
234
+ timeRangeSelect.addEventListener('change', (e) => {
235
+ this.timeRange = e.target.value;
236
+ this.filterEventsByTime();
237
+ });
238
+ }
239
+
240
+ // Search input
241
+ const searchInput = document.getElementById('activity-search');
242
+ if (searchInput) {
243
+ searchInput.addEventListener('input', (e) => {
244
+ this.searchTerm = e.target.value.toLowerCase();
245
+ this.highlightSearchResults();
246
+ });
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Create the D3 visualization
252
+ */
253
+ createVisualization() {
254
+ // Check if D3 is available
255
+ if (typeof d3 === 'undefined') {
256
+ console.error('D3.js is not loaded! Cannot create activity tree visualization.');
257
+ // Try to display an error message in the container
258
+ if (this.container) {
259
+ this.container.innerHTML = '<div style="padding: 20px; text-align: center; color: #e53e3e;">⚠️ D3.js is not loaded. Cannot create visualization.</div>';
260
+ }
261
+ return;
262
+ }
263
+
264
+ // Calculate dimensions based on container
265
+ const containerRect = this.container.getBoundingClientRect();
266
+ this.width = containerRect.width - this.margin.left - this.margin.right;
267
+ this.height = Math.max(500, containerRect.height - this.margin.top - this.margin.bottom);
268
+
269
+ console.log('Creating D3 visualization with dimensions:', { width: this.width, height: this.height });
270
+
271
+ // Clear any existing content (including text)
272
+ d3.select(this.container).selectAll('*').remove();
273
+
274
+ // Create SVG
275
+ this.svg = d3.select(this.container)
276
+ .append('svg')
277
+ .attr('width', '100%')
278
+ .attr('height', '100%')
279
+ .attr('viewBox', `0 0 ${this.width + this.margin.left + this.margin.right} ${this.height + this.margin.top + this.margin.bottom}`);
280
+
281
+ // Create main group for tree positioning
282
+ this.treeGroup = this.svg.append('g')
283
+ .attr('class', 'tree-group')
284
+ .attr('transform', `translate(${this.margin.left},${this.margin.top})`);
285
+
286
+ // Add zoom behavior
287
+ const zoom = d3.zoom()
288
+ .scaleExtent([0.1, 3])
289
+ .on('zoom', (event) => {
290
+ this.treeGroup.attr('transform',
291
+ `translate(${this.margin.left + event.transform.x},${this.margin.top + event.transform.y}) scale(${event.transform.k})`
292
+ );
293
+ });
294
+
295
+ this.svg.call(zoom);
296
+
297
+ // Create tree layout
298
+ this.treeLayout = d3.tree()
299
+ .size([this.height, this.width]);
300
+
301
+ console.log('ActivityTree: Tree layout created:', this.treeLayout);
302
+
303
+ // Create tooltip
304
+ this.tooltip = d3.select('body').append('div')
305
+ .attr('class', 'activity-tooltip')
306
+ .style('opacity', 0);
307
+
308
+ console.log('ActivityTree: Visualization complete, svg:', this.svg, 'treeGroup:', this.treeGroup);
309
+ }
310
+
311
+ /**
312
+ * Initialize tree data structure
313
+ */
314
+ initializeTreeData() {
315
+ console.log('ActivityTree: Initializing tree data');
316
+
317
+ this.treeData = {
318
+ name: 'PM',
319
+ type: 'pm',
320
+ icon: '🎯',
321
+ children: [],
322
+ _children: null
323
+ };
324
+
325
+ // Check if D3 is available
326
+ if (typeof d3 === 'undefined') {
327
+ console.error('ActivityTree: D3 is not available - cannot create hierarchy!');
328
+ // Try to display an error message
329
+ if (this.container) {
330
+ this.container.innerHTML = '<div style="padding: 20px; text-align: center; color: #e53e3e;">⚠️ Waiting for D3.js to load...</div>';
331
+ }
332
+ return;
333
+ }
334
+
335
+ this.root = d3.hierarchy(this.treeData);
336
+ this.root.x0 = this.height / 2;
337
+ this.root.y0 = 0;
338
+
339
+ console.log('ActivityTree: Root node created:', this.root);
340
+
341
+ // Update stats immediately after creating root
342
+ this.updateStats();
343
+ }
344
+
345
+ /**
346
+ * Subscribe to socket events
347
+ */
348
+ subscribeToEvents() {
349
+ if (!window.socketClient) {
350
+ console.warn('Socket client not available for activity tree');
351
+ setTimeout(() => this.subscribeToEvents(), 1000);
352
+ return;
353
+ }
354
+
355
+ console.log('ActivityTree: Setting up event subscription');
356
+
357
+ // Subscribe to event updates from the socket client
358
+ // Process ALL events and determine their type internally
359
+ window.socketClient.onEventUpdate((events) => {
360
+ console.log(`ActivityTree: onEventUpdate called with ${events.length} total events`);
361
+
362
+ // Process only the new events since last update
363
+ const newEventCount = events.length - this.events.length;
364
+ if (newEventCount > 0) {
365
+ // Process only the new events
366
+ const newEvents = events.slice(this.events.length);
367
+
368
+ console.log(`ActivityTree: Processing ${newEventCount} new events`, newEvents);
369
+
370
+ // Process all events, regardless of format
371
+ newEvents.forEach(event => {
372
+ this.processEvent(event);
373
+ });
374
+
375
+ // Update our event count
376
+ this.events = [...events];
377
+ }
378
+ });
379
+
380
+ // Load existing events if available
381
+ const existingEvents = window.socketClient?.events || window.eventViewer?.events || [];
382
+
383
+ if (existingEvents.length > 0) {
384
+ console.log(`ActivityTree: Processing ${existingEvents.length} existing events`, existingEvents);
385
+ existingEvents.forEach(event => {
386
+ this.processEvent(event);
387
+ });
388
+ this.events = [...existingEvents];
389
+ } else {
390
+ console.log('ActivityTree: No existing events found');
391
+ this.events = [];
392
+ }
393
+ }
394
+
395
+ /**
396
+ * Process an event and update the tree
397
+ */
398
+ processEvent(event) {
399
+ if (!event) {
400
+ console.log('ActivityTree: Ignoring null event');
401
+ return;
402
+ }
403
+
404
+ // Handle events with the actual format from the server
405
+ let eventType = null;
406
+
407
+ // First check if hook_event_name exists (from transformation)
408
+ if (event.hook_event_name) {
409
+ eventType = event.hook_event_name;
410
+ }
411
+ // Map from type/subtype for hook events
412
+ else if (event.type === 'hook' && event.subtype) {
413
+ const mapping = {
414
+ 'pre_tool': 'PreToolUse',
415
+ 'post_tool': 'PostToolUse',
416
+ 'subagent_start': 'SubagentStart',
417
+ 'subagent_stop': 'SubagentStop',
418
+ 'todo_write': 'TodoWrite'
419
+ };
420
+ eventType = mapping[event.subtype];
421
+ }
422
+ // Handle todo events
423
+ else if (event.type === 'todo' && event.subtype === 'updated') {
424
+ eventType = 'TodoWrite';
425
+ }
426
+ // Handle subagent events
427
+ else if (event.type === 'subagent') {
428
+ if (event.subtype === 'started') {
429
+ eventType = 'SubagentStart';
430
+ } else if (event.subtype === 'stopped') {
431
+ eventType = 'SubagentStop';
432
+ }
433
+ }
434
+ // Handle start event
435
+ else if (event.type === 'start') {
436
+ eventType = 'Start';
437
+ }
438
+
439
+ if (!eventType) {
440
+ // Only log if it's a potentially relevant event
441
+ if (event.type === 'hook' || event.type === 'todo' || event.type === 'subagent') {
442
+ console.log('ActivityTree: Cannot determine event type for:', event);
443
+ }
444
+ return;
445
+ }
446
+
447
+ console.log(`ActivityTree: Processing event: ${eventType}`, event);
448
+
449
+ const timestamp = new Date(event.timestamp);
450
+ if (!this.isEventInTimeRange(timestamp)) {
451
+ return;
452
+ }
453
+
454
+ switch (eventType) {
455
+ case 'TodoWrite':
456
+ this.processTodoWrite(event);
457
+ break;
458
+ case 'SubagentStart':
459
+ this.processSubagentStart(event);
460
+ break;
461
+ case 'SubagentStop':
462
+ this.processSubagentStop(event);
463
+ break;
464
+ case 'PreToolUse':
465
+ this.processToolUse(event);
466
+ break;
467
+ case 'PostToolUse':
468
+ this.updateToolStatus(event, 'completed');
469
+ break;
470
+ case 'Start':
471
+ this.initializeTreeData();
472
+ this.update(this.root);
473
+ break;
474
+ }
475
+
476
+ this.updateStats();
477
+ }
478
+
479
+ /**
480
+ * Process TodoWrite event
481
+ */
482
+ processTodoWrite(event) {
483
+ console.log('ActivityTree: Processing TodoWrite event:', event);
484
+
485
+ // Look for todos in multiple places for compatibility
486
+ let todos = event.todos ||
487
+ event.data?.todos ||
488
+ event.data || // Sometimes todos are directly in data
489
+ [];
490
+
491
+ // Handle case where todos might be an object with todos property
492
+ if (todos && typeof todos === 'object' && todos.todos) {
493
+ todos = todos.todos;
494
+ }
495
+
496
+ // Ensure todos is an array
497
+ if (!Array.isArray(todos)) {
498
+ console.log('ActivityTree: Invalid todos format in event:', event);
499
+ return;
500
+ }
501
+
502
+ if (todos.length === 0) {
503
+ console.log('ActivityTree: No todos in event');
504
+ return;
505
+ }
506
+
507
+ // Find in-progress todo
508
+ const activeTodo = todos.find(t => t.status === 'in_progress');
509
+ if (!activeTodo) {
510
+ console.log('ActivityTree: No in-progress todo found');
511
+ return;
512
+ }
513
+
514
+ console.log('ActivityTree: Found active todo:', activeTodo);
515
+
516
+ // Create TodoWrite node
517
+ const todoNode = {
518
+ name: activeTodo.activeForm || activeTodo.content,
519
+ type: 'todowrite',
520
+ icon: '📝',
521
+ content: activeTodo.content,
522
+ status: activeTodo.status,
523
+ timestamp: event.timestamp,
524
+ children: [],
525
+ _children: null,
526
+ eventId: event.id
527
+ };
528
+
529
+ // Add to PM root
530
+ if (!this.root) {
531
+ console.error('ActivityTree: No root node!');
532
+ return;
533
+ }
534
+
535
+ if (!this.root.data) {
536
+ console.error('ActivityTree: Root has no data!');
537
+ return;
538
+ }
539
+
540
+ if (!this.root.data.children) {
541
+ this.root.data.children = [];
542
+ }
543
+
544
+ console.log('ActivityTree: Adding TodoWrite node to root');
545
+ this.root.data.children.push(todoNode);
546
+
547
+ // Track this TodoWrite
548
+ this.todoWriteStack.push({
549
+ node: todoNode,
550
+ content: activeTodo.content
551
+ });
552
+
553
+ console.log('ActivityTree: Calling update with root:', this.root);
554
+ this.update(this.root);
555
+ console.log('ActivityTree: Update complete');
556
+ }
557
+
558
+ /**
559
+ * Process SubagentStart event
560
+ */
561
+ processSubagentStart(event) {
562
+ // Look for agent_name in multiple places for compatibility
563
+ const agentName = event.agent_name ||
564
+ event.data?.agent_name ||
565
+ event.data?.agent_type ||
566
+ event.agent_type || // Check direct agent_type field
567
+ event.agent || // Check agent field
568
+ 'unknown';
569
+ const agentIcon = this.getAgentIcon(agentName);
570
+
571
+ // Create agent node
572
+ const agentNode = {
573
+ name: agentName,
574
+ type: 'agent',
575
+ icon: agentIcon,
576
+ timestamp: event.timestamp,
577
+ children: [],
578
+ _children: null,
579
+ eventId: event.id,
580
+ sessionId: event.session_id || event.data?.session_id
581
+ };
582
+
583
+ // Find parent - either last TodoWrite or PM root
584
+ let parent = null;
585
+ if (this.todoWriteStack.length > 0) {
586
+ // Check if TodoWrite mentions this agent
587
+ const todoWrite = this.todoWriteStack[this.todoWriteStack.length - 1];
588
+ if (todoWrite.content && todoWrite.content.toLowerCase().includes(agentName.toLowerCase())) {
589
+ parent = todoWrite.node;
590
+ }
591
+ }
592
+
593
+ if (!parent) {
594
+ parent = this.root.data;
595
+ }
596
+
597
+ if (!parent.children) {
598
+ parent.children = [];
599
+ }
600
+ parent.children.push(agentNode);
601
+
602
+ // Track active agent
603
+ this.activeAgent = agentNode;
604
+ this.activeAgentStack.push(agentNode);
605
+
606
+ this.update(this.root);
607
+ }
608
+
609
+ /**
610
+ * Process SubagentStop event
611
+ */
612
+ processSubagentStop(event) {
613
+ // Mark agent as completed (look for session_id in multiple places)
614
+ const sessionId = event.session_id || event.data?.session_id;
615
+ if (this.activeAgent && this.activeAgent.sessionId === sessionId) {
616
+ this.activeAgent.status = 'completed';
617
+ this.activeAgentStack.pop();
618
+ this.activeAgent = this.activeAgentStack.length > 0 ?
619
+ this.activeAgentStack[this.activeAgentStack.length - 1] : null;
620
+ }
621
+
622
+ this.update(this.root);
623
+ }
624
+
625
+ /**
626
+ * Process tool use event
627
+ */
628
+ processToolUse(event) {
629
+ // Get tool name from various possible locations
630
+ const toolName = event.tool_name ||
631
+ event.data?.tool_name ||
632
+ event.tool || // Check event.tool field
633
+ event.data?.tool ||
634
+ 'unknown';
635
+
636
+ const toolIcon = this.getToolIcon(toolName);
637
+
638
+ // Get parameters from various possible locations
639
+ const params = event.tool_parameters ||
640
+ event.data?.tool_parameters ||
641
+ event.parameters || // Check event.parameters field
642
+ event.data?.parameters ||
643
+ {};
644
+
645
+ // Create tool node
646
+ const toolNode = {
647
+ name: toolName,
648
+ type: 'tool',
649
+ icon: toolIcon,
650
+ timestamp: event.timestamp,
651
+ status: 'in_progress',
652
+ children: [],
653
+ _children: null,
654
+ eventId: event.id
655
+ };
656
+
657
+ // Add file/command as child if applicable
658
+ if (toolName === 'Read' && params.file_path) {
659
+ toolNode.children.push({
660
+ name: params.file_path,
661
+ type: 'file',
662
+ icon: '📄',
663
+ timestamp: event.timestamp
664
+ });
665
+ } else if (toolName === 'Edit' && params.file_path) {
666
+ toolNode.children.push({
667
+ name: params.file_path,
668
+ type: 'file',
669
+ icon: '✏️',
670
+ timestamp: event.timestamp
671
+ });
672
+ } else if (toolName === 'Write' && params.file_path) {
673
+ toolNode.children.push({
674
+ name: params.file_path,
675
+ type: 'file',
676
+ icon: '💾',
677
+ timestamp: event.timestamp
678
+ });
679
+ } else if (toolName === 'Bash' && params.command) {
680
+ toolNode.children.push({
681
+ name: params.command.substring(0, 50) + (params.command.length > 50 ? '...' : ''),
682
+ type: 'command',
683
+ icon: '⚡',
684
+ timestamp: event.timestamp
685
+ });
686
+ } else if (toolName === 'WebFetch' && params.url) {
687
+ toolNode.children.push({
688
+ name: params.url,
689
+ type: 'url',
690
+ icon: '🌐',
691
+ timestamp: event.timestamp
692
+ });
693
+ }
694
+
695
+ // Find parent - active agent or PM root
696
+ let parent = this.activeAgent || this.root.data;
697
+ if (!parent.children) {
698
+ parent.children = [];
699
+ }
700
+ parent.children.push(toolNode);
701
+
702
+ this.update(this.root);
703
+ }
704
+
705
+ /**
706
+ * Update tool status after completion
707
+ */
708
+ updateToolStatus(event, status) {
709
+ // Find tool node by event ID and update status
710
+ const findAndUpdate = (node) => {
711
+ if (node.eventId === event.id) {
712
+ node.status = status;
713
+ return true;
714
+ }
715
+ if (node.children) {
716
+ for (let child of node.children) {
717
+ if (findAndUpdate(child)) return true;
718
+ }
719
+ }
720
+ if (node._children) {
721
+ for (let child of node._children) {
722
+ if (findAndUpdate(child)) return true;
723
+ }
724
+ }
725
+ return false;
726
+ };
727
+
728
+ findAndUpdate(this.root.data);
729
+ this.update(this.root);
730
+ }
731
+
732
+ /**
733
+ * Get agent icon based on name
734
+ */
735
+ getAgentIcon(agentName) {
736
+ const icons = {
737
+ 'engineer': '👷',
738
+ 'research': '🔬',
739
+ 'qa': '🧪',
740
+ 'ops': '⚙️',
741
+ 'pm': '📊',
742
+ 'architect': '🏗️'
743
+ };
744
+ return icons[agentName.toLowerCase()] || '🤖';
745
+ }
746
+
747
+ /**
748
+ * Get tool icon based on name
749
+ */
750
+ getToolIcon(toolName) {
751
+ const icons = {
752
+ 'read': '👁️',
753
+ 'write': '✍️',
754
+ 'edit': '✏️',
755
+ 'bash': '💻',
756
+ 'webfetch': '🌐',
757
+ 'grep': '🔍',
758
+ 'glob': '📂',
759
+ 'todowrite': '📝'
760
+ };
761
+ return icons[toolName.toLowerCase()] || '🔧';
762
+ }
763
+
764
+ /**
765
+ * Update the tree visualization
766
+ */
767
+ update(source) {
768
+ console.log('ActivityTree: update() called with source:', source);
769
+
770
+ // Check if D3 is available
771
+ if (typeof d3 === 'undefined') {
772
+ console.error('ActivityTree: Cannot update - D3.js not loaded');
773
+ return;
774
+ }
775
+
776
+ // Check if visualization is ready
777
+ if (!this.svg || !this.treeGroup) {
778
+ console.warn('ActivityTree: Cannot update - SVG not initialized');
779
+ // Try to create visualization if container exists
780
+ if (this.container) {
781
+ console.log('Attempting to create visualization from update()');
782
+ this.createVisualization();
783
+ // Check again after creation attempt
784
+ if (!this.svg || !this.treeGroup) {
785
+ console.error('Failed to create visualization in update()');
786
+ return;
787
+ }
788
+ } else {
789
+ return;
790
+ }
791
+ }
792
+
793
+ if (!this.treeLayout) {
794
+ console.warn('ActivityTree: Cannot update - tree layout not initialized');
795
+ // Try to create tree layout
796
+ if (typeof d3 !== 'undefined') {
797
+ this.treeLayout = d3.tree().size([this.height, this.width]);
798
+ console.log('Created tree layout in update()');
799
+ } else {
800
+ return;
801
+ }
802
+ }
803
+
804
+ // Ensure source has valid data
805
+ if (!source || !source.data) {
806
+ console.error('ActivityTree: Invalid source in update()', source);
807
+ return;
808
+ }
809
+
810
+ // Ensure we have a valid root
811
+ if (!this.root) {
812
+ console.error('ActivityTree: No root node available for update');
813
+ return;
814
+ }
815
+
816
+ // Compute the new tree layout
817
+ let treeData;
818
+ try {
819
+ treeData = this.treeLayout(this.root);
820
+ } catch (error) {
821
+ console.error('ActivityTree: Error computing tree layout:', error);
822
+ return;
823
+ }
824
+
825
+ const nodes = treeData.descendants();
826
+ const links = treeData.links();
827
+
828
+ console.log(`ActivityTree: Updating tree with ${nodes.length} nodes`);
829
+
830
+ // Check if we actually have the tree container
831
+ if (nodes.length === 1 && this.container) {
832
+ // Only root node exists, ensure container shows the tree
833
+ const svgElement = this.container.querySelector('svg');
834
+ if (!svgElement) {
835
+ console.warn('SVG element not found in container after update');
836
+ }
837
+ }
838
+
839
+ // Normalize for fixed-depth
840
+ nodes.forEach((d) => {
841
+ d.y = d.depth * 180;
842
+ });
843
+
844
+ // Update nodes
845
+ const node = this.treeGroup.selectAll('g.node')
846
+ .data(nodes, (d) => d.id || (d.id = ++this.nodeId));
847
+
848
+ // Enter new nodes
849
+ const nodeEnter = node.enter().append('g')
850
+ .attr('class', 'node')
851
+ .attr('transform', (d) => `translate(${source.y0},${source.x0})`)
852
+ .on('click', (event, d) => this.click(d));
853
+
854
+ // Add circles for nodes
855
+ nodeEnter.append('circle')
856
+ .attr('class', (d) => `node-circle ${d.data.type}`)
857
+ .attr('r', 1e-6)
858
+ .style('fill', (d) => d._children ? this.getNodeColor(d.data.type) : '#fff')
859
+ .style('stroke', (d) => this.getNodeColor(d.data.type));
860
+
861
+ // Add icons
862
+ nodeEnter.append('text')
863
+ .attr('class', 'node-icon')
864
+ .attr('dy', '.35em')
865
+ .attr('text-anchor', 'middle')
866
+ .style('font-size', '14px')
867
+ .text((d) => d.data.icon || '');
868
+
869
+ // Add labels
870
+ nodeEnter.append('text')
871
+ .attr('class', 'node-label')
872
+ .attr('dy', '.35em')
873
+ .attr('x', (d) => d.children || d._children ? -25 : 25)
874
+ .attr('text-anchor', (d) => d.children || d._children ? 'end' : 'start')
875
+ .text((d) => d.data.name)
876
+ .style('fill-opacity', 1e-6);
877
+
878
+ // Add tooltips
879
+ nodeEnter.on('mouseover', (event, d) => this.showTooltip(event, d))
880
+ .on('mouseout', () => this.hideTooltip());
881
+
882
+ // Update existing nodes
883
+ const nodeUpdate = nodeEnter.merge(node);
884
+
885
+ // Transition nodes to new position
886
+ nodeUpdate.transition()
887
+ .duration(this.duration)
888
+ .attr('transform', (d) => `translate(${d.y},${d.x})`);
889
+
890
+ nodeUpdate.select('circle.node-circle')
891
+ .attr('r', 10)
892
+ .style('fill', (d) => {
893
+ if (d.data.status === 'in_progress') {
894
+ return this.getNodeColor(d.data.type);
895
+ }
896
+ return d._children ? this.getNodeColor(d.data.type) : '#fff';
897
+ })
898
+ .attr('class', (d) => {
899
+ let classes = `node-circle ${d.data.type}`;
900
+ if (d.data.status === 'in_progress') classes += ' pulsing';
901
+ if (d.data.status === 'failed') classes += ' failed';
902
+ return classes;
903
+ });
904
+
905
+ nodeUpdate.select('text.node-label')
906
+ .style('fill-opacity', 1);
907
+
908
+ // Remove exiting nodes
909
+ const nodeExit = node.exit().transition()
910
+ .duration(this.duration)
911
+ .attr('transform', (d) => `translate(${source.y},${source.x})`)
912
+ .remove();
913
+
914
+ nodeExit.select('circle')
915
+ .attr('r', 1e-6);
916
+
917
+ nodeExit.select('text')
918
+ .style('fill-opacity', 1e-6);
919
+
920
+ // Update links
921
+ const link = this.treeGroup.selectAll('path.link')
922
+ .data(links, (d) => d.target.id);
923
+
924
+ // Enter new links
925
+ const linkEnter = link.enter().insert('path', 'g')
926
+ .attr('class', 'link')
927
+ .attr('d', (d) => {
928
+ const o = {x: source.x0, y: source.y0};
929
+ return this.diagonal({source: o, target: o});
930
+ });
931
+
932
+ // Update existing links
933
+ const linkUpdate = linkEnter.merge(link);
934
+
935
+ linkUpdate.transition()
936
+ .duration(this.duration)
937
+ .attr('d', this.diagonal);
938
+
939
+ // Remove exiting links
940
+ link.exit().transition()
941
+ .duration(this.duration)
942
+ .attr('d', (d) => {
943
+ const o = {x: source.x, y: source.y};
944
+ return this.diagonal({source: o, target: o});
945
+ })
946
+ .remove();
947
+
948
+ // Store old positions for transition
949
+ nodes.forEach((d) => {
950
+ d.x0 = d.x;
951
+ d.y0 = d.y;
952
+ });
953
+
954
+ // Update breadcrumb on node click
955
+ this.updateBreadcrumb(source);
956
+ }
957
+
958
+ /**
959
+ * Create diagonal path for links
960
+ */
961
+ diagonal(d) {
962
+ return `M ${d.source.y} ${d.source.x}
963
+ C ${(d.source.y + d.target.y) / 2} ${d.source.x},
964
+ ${(d.source.y + d.target.y) / 2} ${d.target.x},
965
+ ${d.target.y} ${d.target.x}`;
966
+ }
967
+
968
+ /**
969
+ * Handle node click for expand/collapse
970
+ */
971
+ click(d) {
972
+ if (d.children) {
973
+ d._children = d.children;
974
+ d.children = null;
975
+ } else {
976
+ d.children = d._children;
977
+ d._children = null;
978
+ }
979
+ this.update(d);
980
+ this.updateBreadcrumb(d);
981
+ }
982
+
983
+ /**
984
+ * Get node color based on type
985
+ */
986
+ getNodeColor(type) {
987
+ const colors = {
988
+ 'pm': '#4299e1',
989
+ 'todowrite': '#48bb78',
990
+ 'agent': '#ed8936',
991
+ 'tool': '#9f7aea',
992
+ 'file': '#38b2ac',
993
+ 'command': '#f56565',
994
+ 'url': '#4299e1'
995
+ };
996
+ return colors[type] || '#718096';
997
+ }
998
+
999
+ /**
1000
+ * Show tooltip
1001
+ */
1002
+ showTooltip(event, d) {
1003
+ const content = `
1004
+ <strong>${d.data.name}</strong><br>
1005
+ Type: ${d.data.type}<br>
1006
+ ${d.data.timestamp ? `Time: ${new Date(d.data.timestamp).toLocaleTimeString()}` : ''}
1007
+ ${d.data.status ? `<br>Status: ${d.data.status}` : ''}
1008
+ `;
1009
+
1010
+ this.tooltip.transition()
1011
+ .duration(200)
1012
+ .style('opacity', .9);
1013
+
1014
+ this.tooltip.html(content)
1015
+ .style('left', (event.pageX + 10) + 'px')
1016
+ .style('top', (event.pageY - 28) + 'px');
1017
+ }
1018
+
1019
+ /**
1020
+ * Hide tooltip
1021
+ */
1022
+ hideTooltip() {
1023
+ this.tooltip.transition()
1024
+ .duration(500)
1025
+ .style('opacity', 0);
1026
+ }
1027
+
1028
+ /**
1029
+ * Expand all nodes
1030
+ */
1031
+ expandAll() {
1032
+ const expand = (d) => {
1033
+ if (d._children) {
1034
+ d.children = d._children;
1035
+ d._children = null;
1036
+ }
1037
+ if (d.children) {
1038
+ d.children.forEach(expand);
1039
+ }
1040
+ };
1041
+
1042
+ expand(this.root);
1043
+ this.update(this.root);
1044
+ }
1045
+
1046
+ /**
1047
+ * Collapse all nodes
1048
+ */
1049
+ collapseAll() {
1050
+ const collapse = (d) => {
1051
+ if (d.children) {
1052
+ d._children = d.children;
1053
+ d._children.forEach(collapse);
1054
+ d.children = null;
1055
+ }
1056
+ };
1057
+
1058
+ this.root.children?.forEach(collapse);
1059
+ this.update(this.root);
1060
+ }
1061
+
1062
+ /**
1063
+ * Reset zoom
1064
+ */
1065
+ resetZoom() {
1066
+ if (!this.svg) {
1067
+ console.warn('Cannot reset zoom: SVG not initialized');
1068
+ return;
1069
+ }
1070
+
1071
+ const zoom = d3.zoom()
1072
+ .scaleExtent([0.1, 3])
1073
+ .on('zoom', (event) => {
1074
+ this.treeGroup.attr('transform',
1075
+ `translate(${this.margin.left + event.transform.x},${this.margin.top + event.transform.y}) scale(${event.transform.k})`
1076
+ );
1077
+ });
1078
+
1079
+ this.svg.transition()
1080
+ .duration(750)
1081
+ .call(zoom.transform, d3.zoomIdentity);
1082
+
1083
+ // Reset the tree group transform
1084
+ this.treeGroup.transition()
1085
+ .duration(750)
1086
+ .attr('transform', `translate(${this.margin.left},${this.margin.top})`);
1087
+ }
1088
+
1089
+ /**
1090
+ * Check if event is in time range
1091
+ */
1092
+ isEventInTimeRange(timestamp) {
1093
+ if (this.timeRange === 'all') return true;
1094
+
1095
+ const now = new Date();
1096
+ const diff = now - timestamp;
1097
+ const minutes = diff / (1000 * 60);
1098
+
1099
+ switch (this.timeRange) {
1100
+ case '10min': return minutes <= 10;
1101
+ case '30min': return minutes <= 30;
1102
+ case 'hour': return minutes <= 60;
1103
+ default: return true;
1104
+ }
1105
+ }
1106
+
1107
+ /**
1108
+ * Filter events by time
1109
+ */
1110
+ filterEventsByTime() {
1111
+ this.initializeTreeData();
1112
+
1113
+ // Reprocess all events with new time filter
1114
+ if (window.eventViewer && window.eventViewer.events) {
1115
+ window.eventViewer.events.forEach(event => {
1116
+ this.processEvent(event);
1117
+ });
1118
+ }
1119
+ }
1120
+
1121
+ /**
1122
+ * Update statistics
1123
+ */
1124
+ updateStats() {
1125
+ // Check if we have a valid root node
1126
+ if (!this.root || !this.root.data) {
1127
+ console.warn('ActivityTree: Cannot update stats - root not initialized');
1128
+ // Set default values
1129
+ const nodeCountEl = document.getElementById('node-count');
1130
+ const activeCountEl = document.getElementById('active-count');
1131
+ const depthEl = document.getElementById('tree-depth');
1132
+
1133
+ if (nodeCountEl) nodeCountEl.textContent = '1';
1134
+ if (activeCountEl) activeCountEl.textContent = '0';
1135
+ if (depthEl) depthEl.textContent = '0';
1136
+ return;
1137
+ }
1138
+
1139
+ const nodeCount = this.countNodes(this.root);
1140
+ const activeCount = this.countActiveNodes(this.root.data);
1141
+ const depth = this.getTreeDepth(this.root);
1142
+
1143
+ const nodeCountEl = document.getElementById('node-count');
1144
+ const activeCountEl = document.getElementById('active-count');
1145
+ const depthEl = document.getElementById('tree-depth');
1146
+
1147
+ if (nodeCountEl) nodeCountEl.textContent = nodeCount;
1148
+ if (activeCountEl) activeCountEl.textContent = activeCount;
1149
+ if (depthEl) depthEl.textContent = depth;
1150
+
1151
+ console.log(`ActivityTree: Stats updated - Nodes: ${nodeCount}, Active: ${activeCount}, Depth: ${depth}`);
1152
+ }
1153
+
1154
+ /**
1155
+ * Count total nodes
1156
+ */
1157
+ countNodes(node) {
1158
+ let count = 1;
1159
+ if (node.children) {
1160
+ node.children.forEach(child => {
1161
+ count += this.countNodes(child);
1162
+ });
1163
+ }
1164
+ if (node._children) {
1165
+ node._children.forEach(child => {
1166
+ count += this.countNodes(child);
1167
+ });
1168
+ }
1169
+ return count;
1170
+ }
1171
+
1172
+ /**
1173
+ * Count active nodes
1174
+ */
1175
+ countActiveNodes(node) {
1176
+ let count = node.status === 'in_progress' ? 1 : 0;
1177
+ if (node.children) {
1178
+ node.children.forEach(child => {
1179
+ count += this.countActiveNodes(child);
1180
+ });
1181
+ }
1182
+ if (node._children) {
1183
+ node._children.forEach(child => {
1184
+ count += this.countActiveNodes(child);
1185
+ });
1186
+ }
1187
+ return count;
1188
+ }
1189
+
1190
+ /**
1191
+ * Get tree depth
1192
+ */
1193
+ getTreeDepth(node) {
1194
+ if (!node.children && !node._children) return 0;
1195
+
1196
+ const children = node.children || node._children;
1197
+ const depths = children.map(child => this.getTreeDepth(child));
1198
+ return Math.max(...depths) + 1;
1199
+ }
1200
+
1201
+ /**
1202
+ * Update breadcrumb
1203
+ */
1204
+ updateBreadcrumb(node) {
1205
+ const path = [];
1206
+ let current = node;
1207
+
1208
+ while (current) {
1209
+ path.unshift(current.data.name);
1210
+ current = current.parent;
1211
+ }
1212
+
1213
+ const breadcrumb = document.getElementById('activity-breadcrumb');
1214
+ if (breadcrumb) {
1215
+ breadcrumb.textContent = path.join(' > ');
1216
+ }
1217
+ }
1218
+
1219
+ /**
1220
+ * Highlight search results
1221
+ */
1222
+ highlightSearchResults() {
1223
+ // Clear previous highlights
1224
+ this.treeGroup.selectAll('.node-label')
1225
+ .style('font-weight', 'normal')
1226
+ .style('fill', '#2d3748');
1227
+
1228
+ if (!this.searchTerm) return;
1229
+
1230
+ // Highlight matching nodes
1231
+ this.treeGroup.selectAll('.node-label')
1232
+ .style('font-weight', d => {
1233
+ return d.data.name.toLowerCase().includes(this.searchTerm) ? 'bold' : 'normal';
1234
+ })
1235
+ .style('fill', d => {
1236
+ return d.data.name.toLowerCase().includes(this.searchTerm) ? '#e53e3e' : '#2d3748';
1237
+ });
1238
+ }
1239
+ }
1240
+
1241
+ // Make ActivityTree globally available immediately when module loads
1242
+ window.ActivityTree = ActivityTree;
1243
+
1244
+ // Initialize when the Activity tab is selected
1245
+ // Only set up event listeners when DOM is ready, but expose class immediately
1246
+ const setupActivityTreeListeners = () => {
1247
+ let activityTree = null;
1248
+
1249
+ // Function to initialize the tree
1250
+ const initializeActivityTree = () => {
1251
+ if (!activityTree) {
1252
+ console.log('Creating new Activity Tree instance...');
1253
+ activityTree = new ActivityTree();
1254
+ // Store instance globally for dashboard access
1255
+ window.activityTreeInstance = activityTree;
1256
+ }
1257
+
1258
+ // Ensure the container is ready and clear any text
1259
+ const container = document.getElementById('activity-tree-container') || document.getElementById('activity-tree');
1260
+ if (container && container.textContent && container.textContent.trim()) {
1261
+ console.log('Clearing text from activity tree container before init:', container.textContent);
1262
+ container.textContent = '';
1263
+ }
1264
+
1265
+ // Always try to initialize when tab becomes active, even if instance exists
1266
+ // Small delay to ensure DOM is ready and tab is visible
1267
+ setTimeout(() => {
1268
+ console.log('Attempting to initialize Activity Tree visualization...');
1269
+ activityTree.initialize();
1270
+ }, 100);
1271
+ };
1272
+
1273
+ // Tab switching logic
1274
+ document.querySelectorAll('.tab-button').forEach(button => {
1275
+ button.addEventListener('click', (e) => {
1276
+ const tabName = e.target.getAttribute('data-tab');
1277
+
1278
+ if (tabName === 'activity') {
1279
+ console.log('Activity tab button clicked, initializing tree...');
1280
+ initializeActivityTree();
1281
+ // Also call renderWhenVisible and forceShow to ensure proper rendering
1282
+ if (activityTree) {
1283
+ setTimeout(() => {
1284
+ activityTree.renderWhenVisible();
1285
+ // Force show to ensure SVG is visible
1286
+ activityTree.forceShow();
1287
+ }, 150);
1288
+ }
1289
+ }
1290
+ });
1291
+ });
1292
+
1293
+ // Also listen for custom tab change events
1294
+ document.addEventListener('tabChanged', (e) => {
1295
+ if (e.detail && e.detail.newTab === 'activity') {
1296
+ console.log('Tab changed to activity, initializing tree...');
1297
+ initializeActivityTree();
1298
+ // Also call renderWhenVisible and forceShow to ensure proper rendering
1299
+ if (activityTree) {
1300
+ setTimeout(() => {
1301
+ activityTree.renderWhenVisible();
1302
+ // Force show to ensure SVG is visible
1303
+ activityTree.forceShow();
1304
+ }, 150);
1305
+ }
1306
+ }
1307
+ });
1308
+
1309
+ // Check if activity tab is already active on load
1310
+ const activeTab = document.querySelector('.tab-button.active');
1311
+ if (activeTab && activeTab.getAttribute('data-tab') === 'activity') {
1312
+ console.log('Activity tab is active on load, initializing tree...');
1313
+ initializeActivityTree();
1314
+ }
1315
+
1316
+ // Also check the tab panel directly
1317
+ const activityPanel = document.getElementById('activity-tab');
1318
+ if (activityPanel && activityPanel.classList.contains('active')) {
1319
+ console.log('Activity panel is active on load, initializing tree...');
1320
+ if (!activityTree) {
1321
+ initializeActivityTree();
1322
+ }
1323
+ }
1324
+
1325
+ // Export for debugging
1326
+ window.activityTree = () => activityTree; // Expose instance getter for debugging
1327
+ };
1328
+
1329
+ // Set up listeners when DOM is ready
1330
+ if (document.readyState === 'loading') {
1331
+ document.addEventListener('DOMContentLoaded', setupActivityTreeListeners);
1332
+ } else {
1333
+ // DOM already loaded
1334
+ setupActivityTreeListeners();
1335
+ }
1336
+
1337
+ export { ActivityTree };
1338
+ export default ActivityTree;