claude-mpm 4.2.44__py3-none-any.whl → 4.2.51__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 (148) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_PM.md +43 -1
  3. claude_mpm/agents/INSTRUCTIONS.md +75 -1
  4. claude_mpm/agents/WORKFLOW.md +46 -1
  5. claude_mpm/agents/frontmatter_validator.py +20 -12
  6. claude_mpm/agents/templates/nextjs_engineer.json +277 -0
  7. claude_mpm/agents/templates/python_engineer.json +289 -0
  8. claude_mpm/agents/templates/react_engineer.json +11 -3
  9. claude_mpm/agents/templates/security.json +50 -9
  10. claude_mpm/cli/commands/agents.py +2 -2
  11. claude_mpm/cli/commands/uninstall.py +1 -2
  12. claude_mpm/cli/interactive/agent_wizard.py +3 -3
  13. claude_mpm/cli/parsers/agent_manager_parser.py +3 -3
  14. claude_mpm/cli/parsers/agents_parser.py +1 -1
  15. claude_mpm/constants.py +1 -1
  16. claude_mpm/core/error_handler.py +2 -4
  17. claude_mpm/core/file_utils.py +4 -12
  18. claude_mpm/core/log_manager.py +8 -5
  19. claude_mpm/core/logger.py +1 -1
  20. claude_mpm/core/logging_utils.py +6 -6
  21. claude_mpm/core/unified_agent_registry.py +18 -4
  22. claude_mpm/dashboard/react/components/DataInspector/DataInspector.module.css +188 -0
  23. claude_mpm/dashboard/react/components/EventViewer/EventViewer.module.css +156 -0
  24. claude_mpm/dashboard/react/components/shared/ConnectionStatus.module.css +38 -0
  25. claude_mpm/dashboard/react/components/shared/FilterBar.module.css +92 -0
  26. claude_mpm/dashboard/static/archive/activity_dashboard_fixed.html +248 -0
  27. claude_mpm/dashboard/static/archive/activity_dashboard_test.html +61 -0
  28. claude_mpm/dashboard/static/archive/test_activity_connection.html +179 -0
  29. claude_mpm/dashboard/static/archive/test_claude_tree_tab.html +68 -0
  30. claude_mpm/dashboard/static/archive/test_dashboard.html +409 -0
  31. claude_mpm/dashboard/static/archive/test_dashboard_fixed.html +519 -0
  32. claude_mpm/dashboard/static/archive/test_dashboard_verification.html +181 -0
  33. claude_mpm/dashboard/static/archive/test_file_data.html +315 -0
  34. claude_mpm/dashboard/static/archive/test_file_tree_empty_state.html +243 -0
  35. claude_mpm/dashboard/static/archive/test_file_tree_fix.html +234 -0
  36. claude_mpm/dashboard/static/archive/test_file_tree_rename.html +117 -0
  37. claude_mpm/dashboard/static/archive/test_file_tree_tab.html +115 -0
  38. claude_mpm/dashboard/static/archive/test_file_viewer.html +224 -0
  39. claude_mpm/dashboard/static/archive/test_final_activity.html +220 -0
  40. claude_mpm/dashboard/static/archive/test_tab_fix.html +139 -0
  41. claude_mpm/dashboard/static/built/assets/events.DjpNxWNo.css +1 -0
  42. claude_mpm/dashboard/static/built/components/activity-tree.js +1 -1
  43. claude_mpm/dashboard/static/built/components/agent-hierarchy.js +777 -0
  44. claude_mpm/dashboard/static/built/components/agent-inference.js +1 -1
  45. claude_mpm/dashboard/static/built/components/build-tracker.js +333 -0
  46. claude_mpm/dashboard/static/built/components/code-simple.js +857 -0
  47. claude_mpm/dashboard/static/built/components/code-tree/tree-breadcrumb.js +353 -0
  48. claude_mpm/dashboard/static/built/components/code-tree/tree-constants.js +235 -0
  49. claude_mpm/dashboard/static/built/components/code-tree/tree-search.js +409 -0
  50. claude_mpm/dashboard/static/built/components/code-tree/tree-utils.js +435 -0
  51. claude_mpm/dashboard/static/built/components/code-viewer.js +2 -1076
  52. claude_mpm/dashboard/static/built/components/connection-debug.js +654 -0
  53. claude_mpm/dashboard/static/built/components/diff-viewer.js +891 -0
  54. claude_mpm/dashboard/static/built/components/event-processor.js +1 -1
  55. claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
  56. claude_mpm/dashboard/static/built/components/export-manager.js +1 -1
  57. claude_mpm/dashboard/static/built/components/file-change-tracker.js +443 -0
  58. claude_mpm/dashboard/static/built/components/file-change-viewer.js +690 -0
  59. claude_mpm/dashboard/static/built/components/file-tool-tracker.js +1 -1
  60. claude_mpm/dashboard/static/built/components/module-viewer.js +1 -1
  61. claude_mpm/dashboard/static/built/components/nav-bar.js +145 -0
  62. claude_mpm/dashboard/static/built/components/page-structure.js +429 -0
  63. claude_mpm/dashboard/static/built/components/session-manager.js +1 -1
  64. claude_mpm/dashboard/static/built/components/ui-state-manager.js +2 -465
  65. claude_mpm/dashboard/static/built/components/working-directory.js +1 -1
  66. claude_mpm/dashboard/static/built/connection-manager.js +536 -0
  67. claude_mpm/dashboard/static/built/dashboard.js +1 -1
  68. claude_mpm/dashboard/static/built/extension-error-handler.js +164 -0
  69. claude_mpm/dashboard/static/built/react/events.js +30 -0
  70. claude_mpm/dashboard/static/built/shared/dom-helpers.js +396 -0
  71. claude_mpm/dashboard/static/built/shared/event-bus.js +330 -0
  72. claude_mpm/dashboard/static/built/shared/event-filter-service.js +540 -0
  73. claude_mpm/dashboard/static/built/shared/logger.js +385 -0
  74. claude_mpm/dashboard/static/built/shared/page-structure.js +251 -0
  75. claude_mpm/dashboard/static/built/shared/tooltip-service.js +253 -0
  76. claude_mpm/dashboard/static/built/socket-client.js +1 -1
  77. claude_mpm/dashboard/static/built/tab-isolation-fix.js +185 -0
  78. claude_mpm/dashboard/static/css/dashboard.css +28 -5
  79. claude_mpm/dashboard/static/dist/assets/events.DjpNxWNo.css +1 -0
  80. claude_mpm/dashboard/static/dist/components/activity-tree.js +1 -1
  81. claude_mpm/dashboard/static/dist/components/agent-inference.js +1 -1
  82. claude_mpm/dashboard/static/dist/components/code-viewer.js +2 -0
  83. claude_mpm/dashboard/static/dist/components/event-processor.js +1 -1
  84. claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
  85. claude_mpm/dashboard/static/dist/components/export-manager.js +1 -1
  86. claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +1 -1
  87. claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
  88. claude_mpm/dashboard/static/dist/components/session-manager.js +1 -1
  89. claude_mpm/dashboard/static/dist/components/working-directory.js +1 -1
  90. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  91. claude_mpm/dashboard/static/dist/react/events.js +30 -0
  92. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  93. claude_mpm/dashboard/static/events.html +607 -0
  94. claude_mpm/dashboard/static/index.html +713 -0
  95. claude_mpm/dashboard/static/js/components/activity-tree.js +3 -17
  96. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +4 -1
  97. claude_mpm/dashboard/static/js/components/agent-inference.js +3 -0
  98. claude_mpm/dashboard/static/js/components/build-tracker.js +8 -0
  99. claude_mpm/dashboard/static/js/components/code-viewer.js +306 -66
  100. claude_mpm/dashboard/static/js/components/event-processor.js +3 -0
  101. claude_mpm/dashboard/static/js/components/event-viewer.js +39 -2
  102. claude_mpm/dashboard/static/js/components/export-manager.js +3 -0
  103. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +30 -10
  104. claude_mpm/dashboard/static/js/components/socket-manager.js +4 -0
  105. claude_mpm/dashboard/static/js/components/ui-state-manager.js +285 -85
  106. claude_mpm/dashboard/static/js/components/working-directory.js +3 -0
  107. claude_mpm/dashboard/static/js/dashboard.js +61 -33
  108. claude_mpm/dashboard/static/js/socket-client.js +12 -8
  109. claude_mpm/dashboard/static/js/stores/dashboard-store.js +562 -0
  110. claude_mpm/dashboard/static/js/tab-isolation-fix.js +185 -0
  111. claude_mpm/dashboard/static/legacy/activity.html +736 -0
  112. claude_mpm/dashboard/static/legacy/agents.html +786 -0
  113. claude_mpm/dashboard/static/legacy/files.html +747 -0
  114. claude_mpm/dashboard/static/legacy/tools.html +831 -0
  115. claude_mpm/dashboard/static/monitors-index.html +218 -0
  116. claude_mpm/dashboard/static/monitors.html +431 -0
  117. claude_mpm/dashboard/static/production/events.html +659 -0
  118. claude_mpm/dashboard/static/production/main.html +715 -0
  119. claude_mpm/dashboard/static/production/monitors.html +483 -0
  120. claude_mpm/dashboard/static/socket.io.min.js +7 -0
  121. claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +7 -0
  122. claude_mpm/dashboard/static/test-archive/dashboard.html +635 -0
  123. claude_mpm/dashboard/static/test-archive/debug-events.html +147 -0
  124. claude_mpm/dashboard/static/test-archive/test-navigation.html +256 -0
  125. claude_mpm/dashboard/static/test-archive/test-react-exports.html +180 -0
  126. claude_mpm/dashboard/templates/index.html +79 -9
  127. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +1 -1
  128. claude_mpm/services/agents/deployment/agent_discovery_service.py +3 -0
  129. claude_mpm/services/agents/deployment/agent_template_builder.py +25 -8
  130. claude_mpm/services/agents/deployment/agent_validator.py +3 -0
  131. claude_mpm/services/agents/deployment/validation/template_validator.py +13 -4
  132. claude_mpm/services/agents/local_template_manager.py +2 -6
  133. claude_mpm/services/monitor/daemon.py +1 -2
  134. claude_mpm/services/monitor/daemon_manager.py +2 -5
  135. claude_mpm/services/monitor/event_emitter.py +2 -2
  136. claude_mpm/services/monitor/handlers/code_analysis.py +4 -6
  137. claude_mpm/services/monitor/handlers/hooks.py +2 -4
  138. claude_mpm/services/monitor/server.py +27 -4
  139. claude_mpm/tools/code_tree_analyzer.py +2 -2
  140. {claude_mpm-4.2.44.dist-info → claude_mpm-4.2.51.dist-info}/METADATA +1 -1
  141. {claude_mpm-4.2.44.dist-info → claude_mpm-4.2.51.dist-info}/RECORD +146 -81
  142. claude_mpm/dashboard/static/test-browser-monitor.html +0 -470
  143. claude_mpm/dashboard/static/test-simple.html +0 -97
  144. /claude_mpm/dashboard/static/{test_debug.html → test-archive/test_debug.html} +0 -0
  145. {claude_mpm-4.2.44.dist-info → claude_mpm-4.2.51.dist-info}/WHEEL +0 -0
  146. {claude_mpm-4.2.44.dist-info → claude_mpm-4.2.51.dist-info}/entry_points.txt +0 -0
  147. {claude_mpm-4.2.44.dist-info → claude_mpm-4.2.51.dist-info}/licenses/LICENSE +0 -0
  148. {claude_mpm-4.2.44.dist-info → claude_mpm-4.2.51.dist-info}/top_level.txt +0 -0
@@ -41,6 +41,14 @@ class EventViewer {
41
41
  this.socketClient.onEventUpdate((events, sessions) => {
42
42
  // Ensure we always have a valid events array
43
43
  this.events = Array.isArray(events) ? events : [];
44
+ console.log('[EventViewer] Events updated - received:', this.events.length);
45
+
46
+ // Update debug metrics
47
+ const debugReceivedEl = document.getElementById('debug-events-received');
48
+ if (debugReceivedEl) {
49
+ debugReceivedEl.textContent = this.events.length;
50
+ }
51
+
44
52
  this.updateDisplay();
45
53
  });
46
54
  }
@@ -221,8 +229,29 @@ class EventViewer {
221
229
  * Render events in the UI
222
230
  */
223
231
  renderEvents() {
224
- const eventsList = document.getElementById('events-list');
225
- if (!eventsList) return;
232
+ // CRITICAL FIX: Use the container passed to constructor, not hardcoded events-list
233
+ // This prevents events from being rendered in the wrong tab
234
+ const eventsList = this.container;
235
+ if (!eventsList) {
236
+ console.warn('[EventViewer] Container not found, skipping render');
237
+ return;
238
+ }
239
+
240
+ // SAFETY: Basic check to ensure we're rendering to the correct container
241
+ if (eventsList.id !== 'events-list') {
242
+ console.error('[EventViewer] CRITICAL: Attempting to render to wrong container:', eventsList.id);
243
+ return;
244
+ }
245
+
246
+ // Check if events tab exists and render regardless of active state
247
+ // This allows events to be pre-rendered when tab becomes active
248
+ const eventsTab = document.getElementById('events-tab');
249
+ if (!eventsTab) {
250
+ console.warn('[EventViewer] Events tab not found in DOM');
251
+ return;
252
+ }
253
+
254
+ console.log('[EventViewer] Rendering events - count:', this.filteredEvents.length);
226
255
 
227
256
  // Check if user is at bottom BEFORE rendering (for autoscroll decision)
228
257
  const wasAtBottom = (eventsList.scrollTop + eventsList.clientHeight >= eventsList.scrollHeight - 10);
@@ -268,6 +297,14 @@ class EventViewer {
268
297
  // Update filtered elements reference
269
298
  this.filteredEventElements = Array.from(eventsList.querySelectorAll('.event-item'));
270
299
 
300
+ console.log('[EventViewer] Events rendered - filtered:', this.filteredEvents.length, 'elements:', this.filteredEventElements.length);
301
+
302
+ // Update debug metrics
303
+ const debugRenderedEl = document.getElementById('debug-events-rendered');
304
+ if (debugRenderedEl) {
305
+ debugRenderedEl.textContent = this.filteredEvents.length;
306
+ }
307
+
271
308
  // Update Dashboard navigation items if we're in the events tab
272
309
  if (window.dashboard && window.dashboard.currentTab === 'events' &&
273
310
  window.dashboard.tabNavigation && window.dashboard.tabNavigation.events) {
@@ -363,3 +363,6 @@ class ExportManager {
363
363
  // ES6 Module export
364
364
  export { ExportManager };
365
365
  export default ExportManager;
366
+
367
+ // Make ExportManager globally available for dist/dashboard.js
368
+ window.ExportManager = ExportManager;
@@ -316,14 +316,24 @@ class FileToolTracker {
316
316
  * @returns {boolean} - True if tool operation
317
317
  */
318
318
  isToolOperation(event) {
319
- // Tool operations have tool_name and are hook events with pre_tool or post_tool subtype
319
+ // Tool operations have tool_name - be more inclusive about event types
320
320
  // Check both top-level and data.tool_name for compatibility
321
321
  const hasToolName = event.tool_name || (event.data && event.data.tool_name);
322
- const isHookEvent = event.type === 'hook';
323
- const isToolSubtype = event.subtype === 'pre_tool' || event.subtype === 'post_tool' ||
324
- (event.subtype && typeof event.subtype === 'string' && event.subtype.includes('tool'));
325
-
326
- return hasToolName && isHookEvent && isToolSubtype;
322
+
323
+ // Accept multiple event types that might contain tool operations
324
+ const validEventTypes = ['hook', 'tool_use', 'tool', 'agent', 'response'];
325
+ const isValidEventType = validEventTypes.includes(event.type) ||
326
+ (event.type && event.type.includes('tool'));
327
+
328
+ // Check for tool-related subtypes or any indication this is a tool operation
329
+ const isToolSubtype = event.subtype === 'pre_tool' ||
330
+ event.subtype === 'post_tool' ||
331
+ (event.subtype && typeof event.subtype === 'string' && event.subtype.includes('tool')) ||
332
+ event.type === 'tool_use' ||
333
+ event.type === 'tool';
334
+
335
+ // If it has a tool_name and either a valid event type or tool subtype, it's a tool operation
336
+ return hasToolName && (isValidEventType || isToolSubtype);
327
337
  }
328
338
 
329
339
  /**
@@ -332,14 +342,20 @@ class FileToolTracker {
332
342
  * @returns {boolean} - True if file operation
333
343
  */
334
344
  isFileOperation(event) {
335
- // File operations are tool events with tools that operate on files
345
+ // File operations are events with file-related tools - be more inclusive
336
346
  // Check both top-level and data for tool_name
337
347
  let toolName = event.tool_name || (event.data && event.data.tool_name) || '';
348
+
349
+ // If no tool name, not a file operation
350
+ if (!toolName) {
351
+ return false;
352
+ }
353
+
338
354
  toolName = toolName.toLowerCase();
339
-
355
+
340
356
  // Check case-insensitively since tool names can come in different cases
341
357
  const fileTools = ['read', 'write', 'edit', 'grep', 'multiedit', 'glob', 'ls', 'bash', 'notebookedit'];
342
-
358
+
343
359
  // Get tool parameters from either location
344
360
  const toolParams = event.tool_parameters || (event.data && event.data.tool_parameters);
345
361
 
@@ -352,7 +368,8 @@ class FileToolTracker {
352
368
  }
353
369
  }
354
370
 
355
- return toolName && fileTools.includes(toolName);
371
+ // If it's a file tool, it's a file operation regardless of event type
372
+ return fileTools.includes(toolName);
356
373
  }
357
374
 
358
375
  /**
@@ -702,3 +719,6 @@ class FileToolTracker {
702
719
  // ES6 Module export
703
720
  export { FileToolTracker };
704
721
  export default FileToolTracker;
722
+
723
+ // Make FileToolTracker globally available for dist/dashboard.js
724
+ window.FileToolTracker = FileToolTracker;
@@ -362,3 +362,7 @@ class SocketManager {
362
362
  // ES6 Module export
363
363
  export { SocketManager };
364
364
  export default SocketManager;
365
+
366
+ // Make SocketManager globally available for the dist/dashboard.js
367
+ // This ensures compatibility with the minified version
368
+ window.SocketManager = SocketManager;
@@ -14,6 +14,9 @@
14
14
  */
15
15
  class UIStateManager {
16
16
  constructor() {
17
+ // Switching lock to prevent race conditions
18
+ this._switching = false;
19
+
17
20
  // Hash to tab mapping
18
21
  this.hashToTab = {
19
22
  '#events': 'events',
@@ -78,6 +81,7 @@ class UIStateManager {
78
81
  */
79
82
  setupEventHandlers() {
80
83
  this.setupHashNavigation();
84
+ this.setupTabClickHandlers(); // Add explicit tab click handlers
81
85
  this.setupUnifiedKeyboardNavigation();
82
86
  }
83
87
 
@@ -103,8 +107,23 @@ class UIStateManager {
103
107
  */
104
108
  handleHashChange() {
105
109
  const hash = window.location.hash || '';
110
+ console.log('[Hash Navigation] DETAILED DEBUG:');
111
+ console.log('[Hash Navigation] - Current hash:', hash);
112
+ console.log('[Hash Navigation] - hashToTab mapping:', this.hashToTab);
113
+ console.log('[Hash Navigation] - Direct lookup result:', this.hashToTab[hash]);
114
+ console.log('[Hash Navigation] - Is hash in mapping?', hash in this.hashToTab);
115
+ console.log('[Hash Navigation] - Hash length:', hash.length);
116
+ console.log('[Hash Navigation] - Hash char codes:', hash.split('').map(c => c.charCodeAt(0)));
117
+
106
118
  const tabName = this.hashToTab[hash] || 'events';
107
- console.log('[Hash Navigation] Switching to tab:', tabName, 'from hash:', hash);
119
+ console.log('[Hash Navigation] Final resolved tab name:', tabName);
120
+
121
+ // Special logging for File Tree tab
122
+ if (tabName === 'claude-tree' || hash === '#file_tree') {
123
+ console.log('[UIStateManager] FILE TREE TAB SELECTED via hash:', hash);
124
+ console.log('[UIStateManager] Tab name resolved to:', tabName);
125
+ }
126
+
108
127
  this.switchTab(tabName, false); // false = don't update hash (we're responding to hash change)
109
128
  }
110
129
 
@@ -116,6 +135,36 @@ class UIStateManager {
116
135
  console.log('[Hash Navigation] setupTabNavigation is deprecated - using hash navigation instead');
117
136
  }
118
137
 
138
+ /**
139
+ * Set up explicit click handlers for tab buttons to ensure proper routing
140
+ * This ensures tab clicks work even if other modules interfere
141
+ */
142
+ setupTabClickHandlers() {
143
+ document.querySelectorAll('.tab-button').forEach(button => {
144
+ button.addEventListener('click', (e) => {
145
+ console.log('[UIStateManager] Tab button clicked:', e.target);
146
+
147
+ // Prevent default only if we're going to handle it
148
+ const tabName = this.getTabNameFromButton(e.target);
149
+ console.log('[UIStateManager] Resolved tab name:', tabName);
150
+
151
+ if (tabName) {
152
+ // Let the href attribute update the hash naturally, which will trigger our hashchange handler
153
+ // But also explicitly trigger the switch in case href doesn't work
154
+ setTimeout(() => {
155
+ const expectedHash = this.tabToHash[tabName];
156
+ if (window.location.hash !== expectedHash && expectedHash) {
157
+ console.log('[UIStateManager] Hash not updated, forcing update:', expectedHash);
158
+ window.location.hash = expectedHash;
159
+ }
160
+ }, 10);
161
+ }
162
+ });
163
+ });
164
+
165
+ console.log('[UIStateManager] Tab click handlers set up for', document.querySelectorAll('.tab-button').length, 'buttons');
166
+ }
167
+
119
168
  /**
120
169
  * Set up unified keyboard navigation across all tabs
121
170
  */
@@ -145,122 +194,270 @@ class UIStateManager {
145
194
  * @returns {string} - Tab name
146
195
  */
147
196
  getTabNameFromButton(button) {
197
+ console.log('[getTabNameFromButton] DEBUG: button object:', button);
198
+ console.log('[getTabNameFromButton] DEBUG: button.nodeType:', button.nodeType);
199
+ console.log('[getTabNameFromButton] DEBUG: button.tagName:', button.tagName);
200
+
201
+ // CRITICAL FIX: Make sure we're dealing with the actual button element
202
+ // Sometimes the click target might be a child element (like the emoji icon)
203
+ let targetButton = button;
204
+ if (button && button.closest && button.closest('.tab-button')) {
205
+ targetButton = button.closest('.tab-button');
206
+ console.log('[getTabNameFromButton] DEBUG: Used closest() to find actual button');
207
+ }
208
+
148
209
  // First check for data-tab attribute
149
- const dataTab = button.getAttribute('data-tab');
150
- if (dataTab) return dataTab;
210
+ const dataTab = targetButton ? targetButton.getAttribute('data-tab') : null;
211
+ console.log('[getTabNameFromButton] DEBUG: data-tab attribute:', dataTab);
212
+ console.log('[getTabNameFromButton] DEBUG: dataTab truthy:', !!dataTab);
213
+
214
+ // CRITICAL: Specifically handle the File Tree case
215
+ if (dataTab === 'claude-tree') {
216
+ console.log('[getTabNameFromButton] DEBUG: Found claude-tree data-tab, returning it');
217
+ return 'claude-tree';
218
+ }
219
+
220
+ if (dataTab) {
221
+ console.log('[getTabNameFromButton] DEBUG: Returning dataTab:', dataTab);
222
+ return dataTab;
223
+ }
151
224
 
152
225
  // Fallback to text content matching
153
- const text = button.textContent.toLowerCase();
154
- if (text.includes('events')) return 'events';
155
- if (text.includes('activity')) return 'activity';
156
- if (text.includes('agents')) return 'agents';
157
- if (text.includes('tools')) return 'tools';
158
- if (text.includes('files')) return 'files';
159
- if (text.includes('file tree')) return 'claude-tree';
226
+ const text = targetButton ? targetButton.textContent.toLowerCase() : '';
227
+ console.log('[getTabNameFromButton] DEBUG: text content:', text);
228
+ console.log('[getTabNameFromButton] DEBUG: text includes file tree:', text.includes('file tree'));
229
+ console.log('[getTabNameFromButton] DEBUG: text includes events:', text.includes('events'));
230
+
231
+ // CRITICAL: Check File Tree FIRST since it's the problematic one
232
+ if (text.includes('file tree') || text.includes('📝')) {
233
+ console.log('[getTabNameFromButton] DEBUG: Matched file tree, returning claude-tree');
234
+ return 'claude-tree';
235
+ }
236
+ if (text.includes('activity') || text.includes('🌳')) return 'activity';
237
+ if (text.includes('agents') || text.includes('🤖')) return 'agents';
238
+ if (text.includes('tools') || text.includes('🔧')) return 'tools';
239
+ if (text.includes('files') || text.includes('📁')) return 'files';
160
240
  if (text.includes('code')) return 'code';
161
241
  if (text.includes('sessions')) return 'sessions';
162
242
  if (text.includes('system')) return 'system';
243
+ if (text.includes('events') || text.includes('📊')) return 'events';
244
+
245
+ console.log('[getTabNameFromButton] DEBUG: No match, falling back to events');
163
246
  return 'events';
164
247
  }
165
248
 
166
249
  /**
167
- * Switch to specified tab
250
+ * Switch to specified tab - BULLETPROOF VERSION
168
251
  * @param {string} tabName - Name of tab to switch to
169
252
  * @param {boolean} updateHash - Whether to update URL hash (default: true)
170
253
  */
171
254
  switchTab(tabName, updateHash = true) {
172
- console.log(`[Hash Navigation] switchTab called with tabName: ${tabName}, updateHash: ${updateHash}`);
173
-
174
- // Update URL hash if requested (when triggered by user action, not hash change)
175
- if (updateHash && this.tabToHash[tabName]) {
176
- const newHash = this.tabToHash[tabName];
177
- if (window.location.hash !== newHash) {
178
- console.log(`[Hash Navigation] Updating hash to: ${newHash}`);
179
- window.location.hash = newHash;
180
- return; // The hashchange event will trigger switchTab again
181
- }
255
+ // CRITICAL: Prevent race conditions by using a switching lock
256
+ if (this._switching) {
257
+ console.log(`[UIStateManager] Tab switch already in progress, queuing: ${tabName}`);
258
+ setTimeout(() => this.switchTab(tabName, updateHash), 50);
259
+ return;
182
260
  }
261
+ this._switching = true;
183
262
 
184
- const previousTab = this.currentTab;
185
- this.currentTab = tabName;
263
+ console.log(`[UIStateManager] BULLETPROOF switchTab: ${tabName}, updateHash: ${updateHash}`);
186
264
 
187
- // Update tab button active states - ensure ALL tabs are deselected first
188
- const allTabButtons = document.querySelectorAll('.tab-button');
189
- allTabButtons.forEach(btn => {
190
- btn.classList.remove('active');
191
- });
192
-
193
- // Now add active class ONLY to the selected tab
194
- allTabButtons.forEach(btn => {
195
- const btnTabName = this.getTabNameFromButton(btn);
196
- if (btnTabName === tabName) {
197
- btn.classList.add('active');
198
- console.log(`[DEBUG] Set active on button with data-tab: ${btn.getAttribute('data-tab')}`);
265
+ try {
266
+ // Extra logging for File Tree debugging
267
+ if (tabName === 'claude-tree') {
268
+ console.log('[UIStateManager] SWITCHING TO FILE TREE TAB');
269
+ console.log('[UIStateManager] Current tab before switch:', this.currentTab);
199
270
  }
271
+
272
+ // Update URL hash if requested (when triggered by user action, not hash change)
273
+ if (updateHash && this.tabToHash[tabName]) {
274
+ const newHash = this.tabToHash[tabName];
275
+ if (window.location.hash !== newHash) {
276
+ console.log(`[UIStateManager] Updating hash to: ${newHash}`);
277
+ this._switching = false; // Release lock before hash change
278
+ window.location.hash = newHash;
279
+ return; // The hashchange event will trigger switchTab again
280
+ }
281
+ }
282
+
283
+ const previousTab = this.currentTab;
284
+ this.currentTab = tabName;
285
+
286
+ // STEP 1: NUCLEAR RESET - Remove ALL active states unconditionally
287
+ this._removeAllActiveStates();
288
+
289
+ // STEP 2: Set the ONE correct tab as active
290
+ this._setActiveTab(tabName);
291
+
292
+ // STEP 3: Show ONLY the correct content
293
+ this._showTabContent(tabName);
294
+
295
+ // STEP 4: Cleanup and validation
296
+ this._validateTabState(tabName);
297
+
298
+ // Clear previous selections when switching tabs
299
+ this.clearUnifiedSelection();
300
+
301
+ // Trigger tab change event for other modules
302
+ document.dispatchEvent(new CustomEvent('tabChanged', {
303
+ detail: {
304
+ newTab: tabName,
305
+ previousTab: previousTab
306
+ }
307
+ }));
308
+
309
+ // Auto-scroll to bottom after a brief delay to ensure content is rendered
310
+ setTimeout(() => {
311
+ if (this.autoScroll) {
312
+ this.scrollCurrentTabToBottom();
313
+ }
314
+
315
+ // Special handling for File Tree tab - trigger the tree render
316
+ // But DON'T let it manipulate tabs itself
317
+ if (tabName === 'claude-tree' && window.CodeViewer) {
318
+ // Call a new method that only renders content, not tab switching
319
+ if (window.CodeViewer.renderContent) {
320
+ window.CodeViewer.renderContent();
321
+ } else {
322
+ // Fallback to show() but it should be fixed to not switch tabs
323
+ window.CodeViewer.show();
324
+ }
325
+ }
326
+ }, 100);
327
+
328
+ } finally {
329
+ // ALWAYS release the lock
330
+ setTimeout(() => {
331
+ this._switching = false;
332
+ }, 200);
333
+ }
334
+ }
335
+
336
+ /**
337
+ * NUCLEAR RESET: Remove ALL active states from ALL elements
338
+ * This ensures no stale states remain
339
+ */
340
+ _removeAllActiveStates() {
341
+ // Remove active class from ALL tab buttons
342
+ document.querySelectorAll('.tab-button').forEach(btn => {
343
+ btn.classList.remove('active');
344
+ // Also remove any inline styling that might interfere
345
+ btn.style.removeProperty('border-bottom');
346
+ btn.style.removeProperty('color');
200
347
  });
201
348
 
202
- // Show/hide tab content using CSS classes - ensure ALL are hidden first
203
- const allTabContents = document.querySelectorAll('.tab-content');
204
- allTabContents.forEach(content => {
349
+ // Remove active class from ALL tab content
350
+ document.querySelectorAll('.tab-content').forEach(content => {
205
351
  content.classList.remove('active');
352
+ // Clear any inline display styles
353
+ content.style.removeProperty('display');
354
+
355
+ // CRITICAL: Clean leaked content in non-events tabs
356
+ if (content.id !== 'events-tab') {
357
+ this._cleanLeakedEventContent(content);
358
+ }
206
359
  });
207
360
 
208
- // Now show ONLY the selected tab content
209
- const activeTab = document.getElementById(`${tabName}-tab`);
210
- if (activeTab) {
211
- activeTab.classList.add('active');
212
- console.log(`[DEBUG] Set active on content: ${tabName}-tab`);
213
-
214
- // Special handling for File Tree tab - ensure it never shows events
361
+ console.log('[UIStateManager] NUCLEAR: All active states removed');
362
+ }
363
+
364
+ /**
365
+ * Set ONLY the specified tab as active
366
+ */
367
+ _setActiveTab(tabName) {
368
+ const targetTab = document.querySelector(`[data-tab="${tabName}"]`);
369
+ if (targetTab) {
370
+ targetTab.classList.add('active');
371
+ console.log(`[UIStateManager] Set active: ${tabName}`);
372
+ } else {
373
+ console.error(`[UIStateManager] Could not find tab button for: ${tabName}`);
374
+ }
375
+ }
376
+
377
+ /**
378
+ * Show ONLY the specified tab content
379
+ */
380
+ _showTabContent(tabName) {
381
+ const targetContent = document.getElementById(`${tabName}-tab`);
382
+ if (targetContent) {
383
+ targetContent.classList.add('active');
384
+ console.log(`[UIStateManager] Showing content: ${tabName}-tab`);
385
+
386
+ // Special handling for File Tree tab
215
387
  if (tabName === 'claude-tree') {
216
- const claudeTreeContainer = document.getElementById('claude-tree-container');
217
- if (claudeTreeContainer) {
218
- // Check if events list somehow got into this container
219
- const eventsList = claudeTreeContainer.querySelector('#events-list');
220
- if (eventsList) {
221
- console.warn('[UIStateManager] Found events-list in File Tree container, removing it!');
222
- eventsList.remove();
223
- }
224
-
225
- // Check for event items
226
- const eventItems = claudeTreeContainer.querySelectorAll('.event-item');
227
- if (eventItems.length > 0) {
228
- console.warn('[UIStateManager] Found event items in File Tree container, clearing!');
229
- eventItems.forEach(item => item.remove());
230
- }
231
- }
388
+ this._prepareFileTreeContent(targetContent);
232
389
  }
390
+ } else {
391
+ console.error(`[UIStateManager] Could not find content for: ${tabName}`);
392
+ }
393
+ }
394
+
395
+ /**
396
+ * Clean any leaked event content from non-event tabs
397
+ */
398
+ _cleanLeakedEventContent(contentElement) {
399
+ // Remove any event items that may have leaked
400
+ const leakedEventItems = contentElement.querySelectorAll('.event-item');
401
+ if (leakedEventItems.length > 0) {
402
+ console.warn(`[UIStateManager] Found ${leakedEventItems.length} leaked event items in ${contentElement.id}, removing...`);
403
+ leakedEventItems.forEach(item => item.remove());
233
404
  }
234
405
 
235
- // Clear previous selections when switching tabs
236
- this.clearUnifiedSelection();
406
+ // Remove any events-list elements
407
+ const leakedEventsList = contentElement.querySelectorAll('#events-list, .events-list');
408
+ if (leakedEventsList.length > 0) {
409
+ console.warn(`[UIStateManager] Found leaked events-list in ${contentElement.id}, removing...`);
410
+ leakedEventsList.forEach(list => list.remove());
411
+ }
412
+ }
237
413
 
238
- // Trigger tab change event for other modules
239
- document.dispatchEvent(new CustomEvent('tabChanged', {
240
- detail: {
241
- newTab: tabName,
242
- previousTab: previousTab
243
- }
244
- }));
414
+ /**
415
+ * Prepare File Tree content area
416
+ */
417
+ _prepareFileTreeContent(fileTreeContent) {
418
+ const claudeTreeContainer = document.getElementById('claude-tree-container');
419
+ if (claudeTreeContainer) {
420
+ // Final cleanup check
421
+ this._cleanLeakedEventContent(claudeTreeContainer);
422
+
423
+ // Ensure container is properly marked for CodeViewer
424
+ claudeTreeContainer.setAttribute('data-owner', 'code-viewer');
425
+ claudeTreeContainer.setAttribute('data-component', 'CodeViewer');
426
+
427
+ console.log('[UIStateManager] File Tree container prepared');
428
+ }
429
+ }
245
430
 
246
- // Auto-scroll to bottom after a brief delay to ensure content is rendered
431
+ /**
432
+ * Validate that tab state is correct after switching
433
+ */
434
+ _validateTabState(expectedTab) {
247
435
  setTimeout(() => {
248
- if (this.autoScroll) {
249
- this.scrollCurrentTabToBottom();
436
+ const activeTabs = document.querySelectorAll('.tab-button.active');
437
+ const activeContents = document.querySelectorAll('.tab-content.active');
438
+
439
+ if (activeTabs.length !== 1) {
440
+ console.error(`[UIStateManager] VALIDATION FAILED: Expected 1 active tab, found ${activeTabs.length}`);
441
+ activeTabs.forEach((tab, idx) => {
442
+ console.error(` - Active tab ${idx + 1}: ${tab.textContent.trim()} (${tab.getAttribute('data-tab')})`);
443
+ });
444
+ // Force fix
445
+ this._removeAllActiveStates();
446
+ this._setActiveTab(expectedTab);
250
447
  }
251
-
252
- // Special handling for File Tree tab - trigger the tree render
253
- // But DON'T let it manipulate tabs itself
254
- if (tabName === 'claude-tree' && window.CodeViewer) {
255
- // Call a new method that only renders content, not tab switching
256
- if (window.CodeViewer.renderContent) {
257
- window.CodeViewer.renderContent();
258
- } else {
259
- // Fallback to show() but it should be fixed to not switch tabs
260
- window.CodeViewer.show();
261
- }
448
+
449
+ if (activeContents.length !== 1) {
450
+ console.error(`[UIStateManager] VALIDATION FAILED: Expected 1 active content, found ${activeContents.length}`);
451
+ activeContents.forEach((content, idx) => {
452
+ console.error(` - Active content ${idx + 1}: ${content.id}`);
453
+ });
454
+ // Force fix
455
+ this._removeAllActiveStates();
456
+ this._showTabContent(expectedTab);
262
457
  }
263
- }, 100);
458
+
459
+ console.log(`[UIStateManager] Tab state validated for: ${expectedTab}`);
460
+ }, 50);
264
461
  }
265
462
 
266
463
  /**
@@ -547,3 +744,6 @@ class UIStateManager {
547
744
  // ES6 Module export
548
745
  export { UIStateManager };
549
746
  export default UIStateManager;
747
+
748
+ // Make UIStateManager globally available for dist/dashboard.js
749
+ window.UIStateManager = UIStateManager;
@@ -915,3 +915,6 @@ class WorkingDirectoryManager {
915
915
  // ES6 Module export
916
916
  export { WorkingDirectoryManager };
917
917
  export default WorkingDirectoryManager;
918
+
919
+ // Make WorkingDirectoryManager globally available for dist/dashboard.js
920
+ window.WorkingDirectoryManager = WorkingDirectoryManager;