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
@@ -1,1076 +1,2 @@
1
- /**
2
- * Code Viewer Component - Claude Activity Tree Viewer
3
- *
4
- * Shows a D3.js tree visualization of files Claude has viewed or edited,
5
- * including AST paths (classes, functions, methods) extracted from the files.
6
- * This is NOT a directory viewer but an activity-focused visualization.
7
- * Renders in the Claude Tree tab of the dashboard.
8
- */
9
-
10
- class CodeViewer {
11
- constructor() {
12
- this.container = null;
13
- this.svg = null;
14
- this.initialized = false;
15
- this.fileActivity = new Map(); // Map of file path to activity data
16
- this.sessions = new Map();
17
- this.currentSession = null;
18
- this.treeData = null;
19
- this.d3Tree = null;
20
- this.d3Root = null;
21
- this.selectedNode = null;
22
- this.width = 800;
23
- this.height = 600;
24
- this.nodeRadius = 5;
25
- this.renderInProgress = false; // Prevent concurrent renders
26
- this.containerObserver = null;
27
- }
28
-
29
- /**
30
- * Initialize the code viewer
31
- */
32
- initialize() {
33
- if (this.initialized) {
34
- console.log('[CodeViewer] Already initialized, skipping');
35
- return;
36
- }
37
-
38
- console.log('[CodeViewer] Initializing...');
39
- this.setupContainer();
40
- this.setupEventHandlers();
41
- this.subscribeToEvents();
42
- this.processExistingEvents();
43
-
44
- this.initialized = true;
45
- console.log('[CodeViewer] Code Viewer (Claude Activity Tree) initialized successfully');
46
- }
47
-
48
- /**
49
- * Setup the container in the Claude Tree tab
50
- */
51
- setupContainer() {
52
- // Find the Claude Tree tab container
53
- const treeContainer = document.getElementById('claude-tree-container');
54
- if (!treeContainer) {
55
- console.error('Claude Tree container not found');
56
- return;
57
- }
58
-
59
- // Store the container reference
60
- this.container = treeContainer;
61
-
62
- // Setup the activity tree interface
63
- this.renderInterface();
64
- }
65
-
66
- /**
67
- * Render the activity tree interface in the Claude Tree tab
68
- */
69
- renderInterface() {
70
- if (!this.container) {
71
- console.error('[CodeViewer] Container not found, cannot render interface');
72
- return;
73
- }
74
-
75
- // Prevent concurrent renders
76
- if (this.renderInProgress) {
77
- console.log('[CodeViewer] Render already in progress, skipping');
78
- return;
79
- }
80
-
81
- // Check if interface already exists and is intact
82
- const existingWrapper = this.container.querySelector('.activity-tree-wrapper');
83
- const existingSvg = this.container.querySelector('#claude-activity-tree-svg');
84
- if (existingWrapper && existingSvg) {
85
- console.log('[CodeViewer] Interface already exists and is intact, skipping render');
86
- return;
87
- }
88
-
89
- this.renderInProgress = true;
90
- console.log('[CodeViewer] Rendering interface in container:', this.container.id);
91
-
92
- // Temporarily disconnect observer to prevent loops
93
- if (this.containerObserver) {
94
- this.containerObserver.disconnect();
95
- }
96
-
97
- // Clear any existing content completely
98
- this.container.innerHTML = '';
99
-
100
- // Create the activity tree interface (without redundant session selector)
101
- this.container.innerHTML = `
102
- <div class="activity-tree-wrapper" style="height: 100%; display: flex; flex-direction: column;">
103
- <div class="activity-controls" style="padding: 10px; border-bottom: 1px solid #ddd; background: #f9f9f9; display: flex; align-items: center; gap: 10px;">
104
- <button id="claude-expand-all-btn" class="control-btn" style="padding: 4px 8px; font-size: 0.9em;">Expand All</button>
105
- <button id="claude-collapse-all-btn" class="control-btn" style="padding: 4px 8px; font-size: 0.9em;">Collapse All</button>
106
- <button id="claude-reset-zoom-btn" class="control-btn" style="padding: 4px 8px; font-size: 0.9em;">Reset Zoom</button>
107
- <div class="stats" id="claude-tree-stats" style="margin-left: auto; font-size: 0.9em; color: #666;"></div>
108
- </div>
109
- <div class="tree-container" id="claude-tree-svg-container" style="flex: 1; overflow: hidden; position: relative; background: white;">
110
- <svg id="claude-activity-tree-svg" style="width: 100%; height: 100%;"></svg>
111
- </div>
112
- <div class="legend" style="padding: 5px 10px; border-top: 1px solid #ddd; background: #f9f9f9; font-size: 0.85em; display: flex; gap: 15px;">
113
- <span class="legend-item"><span style="color: #4CAF50;">●</span> File</span>
114
- <span class="legend-item"><span style="color: #2196F3;">●</span> Class</span>
115
- <span class="legend-item"><span style="color: #FF9800;">●</span> Function</span>
116
- <span class="legend-item"><span style="color: #9C27B0;">●</span> Method</span>
117
- <span class="legend-item"><span style="color: #F44336;">◆</span> Edited</span>
118
- <span class="legend-item"><span style="color: #4CAF50;">○</span> Viewed</span>
119
- </div>
120
- </div>
121
- `;
122
-
123
- // Get container dimensions for tree sizing
124
- const svgContainer = document.getElementById('claude-tree-svg-container');
125
- if (svgContainer) {
126
- const rect = svgContainer.getBoundingClientRect();
127
- this.width = rect.width || 800;
128
- this.height = rect.height || 600;
129
- }
130
-
131
- // Mark render as complete and re-enable observer if needed
132
- this.renderInProgress = false;
133
-
134
- // Re-enable container protection after render
135
- if (this.containerObserver && this.container) {
136
- this.containerObserver.observe(this.container, {
137
- childList: true,
138
- subtree: false // Only watch direct children, not subtree
139
- });
140
- }
141
- }
142
-
143
- /**
144
- * Show the activity tree (switch to Claude Tree tab and render)
145
- */
146
- show() {
147
- console.log('[CodeViewer] show() called');
148
-
149
- // Switch to the Claude Tree tab if we're being called from tab switch
150
- const claudeTreeTab = document.querySelector('[data-tab="claude-tree"]');
151
- const claudeTreeContent = document.getElementById('claude-tree-tab');
152
-
153
- if (claudeTreeTab && claudeTreeContent) {
154
- // Only switch tabs if not already active
155
- if (!claudeTreeContent.classList.contains('active')) {
156
- // Remove active class from all tabs and contents
157
- document.querySelectorAll('.tab-button').forEach(btn => btn.classList.remove('active'));
158
- document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
159
-
160
- // Add active class to Claude Tree tab
161
- claudeTreeTab.classList.add('active');
162
- claudeTreeContent.classList.add('active');
163
- }
164
- }
165
-
166
- // Get the claude tree container
167
- const claudeTreeContainer = document.getElementById('claude-tree-container');
168
- if (!claudeTreeContainer) {
169
- console.error('[CodeViewer] Claude Tree container not found!');
170
- return;
171
- }
172
-
173
- // CRITICAL: Prevent other components from writing to this container
174
- // Add multiple attributes to mark ownership strongly
175
- claudeTreeContainer.setAttribute('data-owner', 'code-viewer');
176
- claudeTreeContainer.setAttribute('data-tab-reserved', 'claude-tree');
177
- claudeTreeContainer.setAttribute('data-component', 'CodeViewer');
178
-
179
- // Store the container reference if not already set
180
- if (!this.container || this.container !== claudeTreeContainer) {
181
- this.container = claudeTreeContainer;
182
- }
183
-
184
- // Initialize if needed (this will setup container and render interface)
185
- if (!this.initialized) {
186
- this.initialize();
187
- } else {
188
- // Only render interface if it doesn't exist
189
- const existingWrapper = this.container.querySelector('.activity-tree-wrapper');
190
- if (!existingWrapper) {
191
- console.log('[CodeViewer] Interface missing, rendering...');
192
- this.renderInterface();
193
- }
194
- }
195
-
196
- // Set up mutation observer to protect container (only if not already set)
197
- if (!this.containerObserver) {
198
- this.protectContainer();
199
- }
200
-
201
- // Setup event handlers for the new controls
202
- this.setupControlHandlers();
203
-
204
- // Get current session from main selector
205
- const mainSessionSelect = document.getElementById('session-select');
206
- if (mainSessionSelect) {
207
- this.currentSession = mainSessionSelect.value || null;
208
- }
209
-
210
- // Build and render tree
211
- this.buildTreeData();
212
- this.renderTree();
213
-
214
- // Update stats
215
- this.updateStats();
216
-
217
- console.log('[CodeViewer] show() completed, container should now have tree interface');
218
- }
219
-
220
- /**
221
- * Protect the container from being overwritten by other components
222
- */
223
- protectContainer() {
224
- const container = document.getElementById('claude-tree-container');
225
- if (!container) return;
226
-
227
- // Disconnect any existing observer
228
- if (this.containerObserver) {
229
- this.containerObserver.disconnect();
230
- }
231
-
232
- // Flag to prevent re-render loops
233
- let reRenderScheduled = false;
234
-
235
- // Create a new observer to watch for unwanted changes
236
- this.containerObserver = new MutationObserver((mutations) => {
237
- for (const mutation of mutations) {
238
- // Check if nodes were added that shouldn't be there
239
- for (const node of mutation.addedNodes) {
240
- if (node.nodeType === Node.ELEMENT_NODE) {
241
- const element = node;
242
-
243
- // AGGRESSIVE filtering: Block ANY content that's not our tree interface
244
- const isUnwantedContent = (
245
- element.classList?.contains('event-item') ||
246
- element.classList?.contains('events-list') ||
247
- element.classList?.contains('no-events') ||
248
- element.id === 'events-list' ||
249
- (element.textContent && (
250
- element.textContent.includes('[hook]') ||
251
- element.textContent.includes('hook.user_prompt') ||
252
- element.textContent.includes('hook.pre_tool') ||
253
- element.textContent.includes('hook.post_tool') ||
254
- element.textContent.includes('Connect to Socket.IO') ||
255
- element.textContent.includes('No events')
256
- )) ||
257
- // Block any div without our expected classes
258
- (element.tagName === 'DIV' &&
259
- !element.classList?.contains('activity-tree-wrapper') &&
260
- !element.classList?.contains('activity-controls') &&
261
- !element.classList?.contains('tree-container') &&
262
- !element.classList?.contains('legend') &&
263
- !element.id?.startsWith('claude-'))
264
- );
265
-
266
- if (isUnwantedContent) {
267
- console.warn('[CodeViewer] BLOCKED unwanted content in Claude Tree container:', element);
268
- console.warn('[CodeViewer] Element classes:', element.classList?.toString());
269
- console.warn('[CodeViewer] Element text preview:', element.textContent?.substring(0, 100));
270
-
271
- // Remove the unwanted content immediately
272
- try {
273
- node.remove();
274
- } catch (e) {
275
- console.warn('[CodeViewer] Failed to remove unwanted node:', e);
276
- }
277
-
278
- // Schedule a single re-render if needed
279
- if (!reRenderScheduled && !this.renderInProgress) {
280
- reRenderScheduled = true;
281
- setTimeout(() => {
282
- reRenderScheduled = false;
283
- if (!container.querySelector('.activity-tree-wrapper')) {
284
- console.log('[CodeViewer] Re-rendering interface after blocking unwanted content');
285
- this.renderInterface();
286
- this.setupControlHandlers();
287
- this.buildTreeData();
288
- this.renderTree();
289
- }
290
- }, 50);
291
- }
292
- }
293
- }
294
- }
295
-
296
- // Also check if our content was removed
297
- if (mutation.type === 'childList' && mutation.removedNodes.length > 0) {
298
- for (const node of mutation.removedNodes) {
299
- if (node.nodeType === Node.ELEMENT_NODE) {
300
- const element = node;
301
- if (element.classList?.contains('activity-tree-wrapper')) {
302
- console.warn('[CodeViewer] Our tree interface was removed! Re-rendering...');
303
- if (!reRenderScheduled && !this.renderInProgress) {
304
- reRenderScheduled = true;
305
- setTimeout(() => {
306
- reRenderScheduled = false;
307
- this.renderInterface();
308
- this.setupControlHandlers();
309
- this.buildTreeData();
310
- this.renderTree();
311
- }, 50);
312
- }
313
- }
314
- }
315
- }
316
- }
317
- }
318
- });
319
-
320
- // Start observing only direct children to reduce overhead
321
- this.containerObserver.observe(container, {
322
- childList: true,
323
- subtree: false // Only watch direct children, not entire subtree
324
- });
325
-
326
- console.log('[CodeViewer] Container protection enabled with aggressive filtering');
327
- }
328
-
329
- /**
330
- * Setup event handlers for controls
331
- */
332
- setupControlHandlers() {
333
- // Listen to main session selector changes
334
- const mainSessionSelect = document.getElementById('session-select');
335
- if (mainSessionSelect && !mainSessionSelect.hasAttribute('data-tree-listener')) {
336
- mainSessionSelect.setAttribute('data-tree-listener', 'true');
337
- mainSessionSelect.addEventListener('change', (e) => {
338
- this.currentSession = e.target.value || null;
339
- console.log('[CodeViewer] Session changed to:', this.currentSession);
340
- if (this.isTabActive()) {
341
- this.buildTreeData();
342
- this.renderTree();
343
- this.updateStats();
344
- }
345
- });
346
- }
347
-
348
- // Expand all button
349
- const expandBtn = document.getElementById('claude-expand-all-btn');
350
- if (expandBtn && !expandBtn.hasAttribute('data-listener')) {
351
- expandBtn.setAttribute('data-listener', 'true');
352
- expandBtn.addEventListener('click', () => {
353
- this.expandAllNodes();
354
- });
355
- }
356
-
357
- // Collapse all button
358
- const collapseBtn = document.getElementById('claude-collapse-all-btn');
359
- if (collapseBtn && !collapseBtn.hasAttribute('data-listener')) {
360
- collapseBtn.setAttribute('data-listener', 'true');
361
- collapseBtn.addEventListener('click', () => {
362
- this.collapseAllNodes();
363
- });
364
- }
365
-
366
- // Reset zoom button
367
- const resetBtn = document.getElementById('claude-reset-zoom-btn');
368
- if (resetBtn && !resetBtn.hasAttribute('data-listener')) {
369
- resetBtn.setAttribute('data-listener', 'true');
370
- resetBtn.addEventListener('click', () => {
371
- this.resetZoom();
372
- });
373
- }
374
- }
375
-
376
- /**
377
- * Setup event handlers
378
- */
379
- setupEventHandlers() {
380
- // Tab handling is done in show() method
381
- }
382
-
383
- /**
384
- * Subscribe to events from socket and event bus
385
- */
386
- subscribeToEvents() {
387
- // Listen for claude events from socket
388
- if (window.socket) {
389
- window.socket.on('claude_event', (event) => {
390
- console.log('[CodeViewer] Received claude_event:', event);
391
- if (this.isFileOperationEvent(event)) {
392
- this.processClaudeEvent(event);
393
- // Only update if the Claude Tree tab is active
394
- if (this.isTabActive()) {
395
- this.buildTreeData();
396
- this.renderTree();
397
- this.updateStats();
398
- }
399
- }
400
- });
401
- }
402
-
403
- // Listen for events from event bus
404
- if (window.eventBus) {
405
- window.eventBus.on('claude_event', (event) => {
406
- console.log('[CodeViewer] Received claude_event from eventBus:', event);
407
- if (this.isFileOperationEvent(event)) {
408
- this.processClaudeEvent(event);
409
- // Only update if the Claude Tree tab is active
410
- if (this.isTabActive()) {
411
- this.buildTreeData();
412
- this.renderTree();
413
- this.updateStats();
414
- }
415
- }
416
- });
417
- }
418
- }
419
-
420
- /**
421
- * Check if Claude Tree tab is active
422
- */
423
- isTabActive() {
424
- const claudeTreeContent = document.getElementById('claude-tree-tab');
425
- return claudeTreeContent && claudeTreeContent.classList.contains('active');
426
- }
427
-
428
- /**
429
- * Process existing events from dashboard
430
- */
431
- processExistingEvents() {
432
- if (window.dashboard && window.dashboard.eventStore) {
433
- const events = window.dashboard.eventStore.getAllEvents();
434
- events.forEach(event => {
435
- if (this.isFileOperationEvent(event)) {
436
- this.processClaudeEvent(event);
437
- }
438
- });
439
- }
440
- }
441
-
442
- /**
443
- * Check if an event is a file operation event
444
- */
445
- isFileOperationEvent(event) {
446
- // Check if this is a hook event with file operation tool
447
- if (event.type === 'hook' &&
448
- (event.subtype === 'pre_tool' || event.subtype === 'post_tool') &&
449
- event.data && event.data.tool_name) {
450
- const fileOps = ['Read', 'Write', 'Edit', 'MultiEdit', 'NotebookEdit'];
451
- return fileOps.includes(event.data.tool_name);
452
- }
453
- return false;
454
- }
455
-
456
- /**
457
- * Check if an event is a file operation (legacy format)
458
- */
459
- isFileOperation(event) {
460
- const fileOps = ['Read', 'Write', 'Edit', 'MultiEdit', 'NotebookEdit'];
461
- return fileOps.includes(event.tool_name);
462
- }
463
-
464
- /**
465
- * Process a claude event with file operation
466
- */
467
- processClaudeEvent(event) {
468
- if (!this.isFileOperationEvent(event)) return;
469
-
470
- // Extract data from claude_event structure
471
- const data = event.data || {};
472
- const tool_name = data.tool_name;
473
- const tool_parameters = data.tool_parameters || {};
474
- const tool_output = data.tool_output;
475
- const timestamp = event.timestamp || new Date().toISOString();
476
- const session_id = event.session_id || data.session_id;
477
- const working_directory = data.working_directory || '/';
478
-
479
- const filePath = tool_parameters.file_path || tool_parameters.notebook_path;
480
-
481
- console.log('[CodeViewer] Processing file operation:', tool_name, filePath);
482
-
483
- this.processFileOperation({
484
- tool_name,
485
- tool_parameters,
486
- tool_output,
487
- timestamp,
488
- session_id,
489
- working_directory,
490
- filePath
491
- });
492
- }
493
-
494
- /**
495
- * Process a file operation event (legacy format)
496
- */
497
- processEvent(event) {
498
- if (!this.isFileOperation(event)) return;
499
-
500
- const { tool_name, tool_parameters, tool_output, timestamp, session_id, working_directory } = event;
501
- const filePath = tool_parameters?.file_path || tool_parameters?.notebook_path;
502
-
503
- this.processFileOperation({
504
- tool_name,
505
- tool_parameters,
506
- tool_output,
507
- timestamp,
508
- session_id,
509
- working_directory,
510
- filePath
511
- });
512
- }
513
-
514
- /**
515
- * Process a file operation
516
- */
517
- processFileOperation({ tool_name, tool_parameters, tool_output, timestamp, session_id, working_directory, filePath }) {
518
- if (!filePath) return;
519
-
520
- // Track session
521
- if (session_id && !this.sessions.has(session_id)) {
522
- this.sessions.set(session_id, {
523
- id: session_id,
524
- working_directory: working_directory || '/',
525
- files: new Set()
526
- });
527
- // Update session list when new session is added
528
- this.updateSessionList();
529
- }
530
-
531
- // Get or create file activity
532
- if (!this.fileActivity.has(filePath)) {
533
- this.fileActivity.set(filePath, {
534
- path: filePath,
535
- operations: [],
536
- sessions: new Set(),
537
- working_directories: new Set(),
538
- lastContent: null,
539
- astPaths: []
540
- });
541
- }
542
-
543
- const activity = this.fileActivity.get(filePath);
544
-
545
- // Add operation
546
- activity.operations.push({
547
- type: tool_name,
548
- timestamp: timestamp,
549
- parameters: tool_parameters,
550
- output: tool_output,
551
- session_id: session_id
552
- });
553
-
554
- // Track session and working directory
555
- if (session_id) {
556
- activity.sessions.add(session_id);
557
- const session = this.sessions.get(session_id);
558
- if (session) {
559
- session.files.add(filePath);
560
- }
561
- }
562
- if (working_directory) {
563
- activity.working_directories.add(working_directory);
564
- }
565
-
566
- // Update content and extract AST if applicable
567
- if (tool_name === 'Write' && tool_parameters.content) {
568
- activity.lastContent = tool_parameters.content;
569
- activity.astPaths = this.extractASTPaths(tool_parameters.content, filePath);
570
- } else if (tool_name === 'Read' && tool_output?.content) {
571
- activity.lastContent = tool_output.content;
572
- activity.astPaths = this.extractASTPaths(tool_output.content, filePath);
573
- } else if (tool_name === 'Edit' && activity.lastContent) {
574
- // Apply edit to content if we have it
575
- const oldString = tool_parameters.old_string;
576
- const newString = tool_parameters.new_string;
577
- if (oldString && newString) {
578
- activity.lastContent = activity.lastContent.replace(oldString, newString);
579
- activity.astPaths = this.extractASTPaths(activity.lastContent, filePath);
580
- }
581
- }
582
-
583
- console.log('[CodeViewer] File activity updated:', filePath, 'Total files:', this.fileActivity.size)
584
- }
585
-
586
- /**
587
- * Extract AST paths from code content
588
- */
589
- extractASTPaths(content, filePath) {
590
- if (!content || typeof content !== 'string') return [];
591
-
592
- const ext = filePath.split('.').pop()?.toLowerCase();
593
- const paths = [];
594
-
595
- if (ext === 'py') {
596
- // Python: Extract classes, functions, and methods
597
- const classRegex = /^class\s+(\w+)/gm;
598
- const functionRegex = /^def\s+(\w+)/gm;
599
- const methodRegex = /^\s{4,}def\s+(\w+)/gm;
600
-
601
- let match;
602
- while ((match = classRegex.exec(content)) !== null) {
603
- paths.push({ name: match[1], type: 'class' });
604
- }
605
- while ((match = functionRegex.exec(content)) !== null) {
606
- paths.push({ name: match[1], type: 'function' });
607
- }
608
- while ((match = methodRegex.exec(content)) !== null) {
609
- if (!paths.some(p => p.name === match[1])) {
610
- paths.push({ name: match[1], type: 'method' });
611
- }
612
- }
613
- } else if (ext === 'js' || ext === 'jsx' || ext === 'ts' || ext === 'tsx') {
614
- // JavaScript/TypeScript: Extract classes, functions, methods
615
- const classRegex = /class\s+(\w+)/g;
616
- const functionRegex = /function\s+(\w+)/g;
617
- const arrowFunctionRegex = /const\s+(\w+)\s*=\s*\([^)]*\)\s*=>/g;
618
- const methodRegex = /(\w+)\s*\([^)]*\)\s*\{/g;
619
-
620
- let match;
621
- while ((match = classRegex.exec(content)) !== null) {
622
- paths.push({ name: match[1], type: 'class' });
623
- }
624
- while ((match = functionRegex.exec(content)) !== null) {
625
- paths.push({ name: match[1], type: 'function' });
626
- }
627
- while ((match = arrowFunctionRegex.exec(content)) !== null) {
628
- paths.push({ name: match[1], type: 'function' });
629
- }
630
- }
631
-
632
- return paths;
633
- }
634
-
635
- /**
636
- * Build tree data from file activity
637
- */
638
- buildTreeData() {
639
- const root = {
640
- name: 'Claude Activity',
641
- type: 'root',
642
- children: []
643
- };
644
-
645
- // Group by working directory
646
- const dirMap = new Map();
647
-
648
- for (const [filePath, activity] of this.fileActivity.entries()) {
649
- // Filter by session if selected
650
- if (this.currentSession) {
651
- if (!activity.sessions.has(this.currentSession)) {
652
- continue;
653
- }
654
- }
655
-
656
- // Determine working directory
657
- const workingDirs = Array.from(activity.working_directories);
658
- const workingDir = workingDirs[0] || '/';
659
-
660
- if (!dirMap.has(workingDir)) {
661
- dirMap.set(workingDir, {
662
- name: workingDir.split('/').pop() || workingDir,
663
- path: workingDir,
664
- type: 'directory',
665
- children: []
666
- });
667
- }
668
-
669
- // Create file node
670
- const fileName = filePath.split('/').pop();
671
- const hasEdits = activity.operations.some(op => op.type === 'Edit' || op.type === 'Write');
672
-
673
- const fileNode = {
674
- name: fileName,
675
- path: filePath,
676
- type: 'file',
677
- edited: hasEdits,
678
- operations: activity.operations.length,
679
- children: []
680
- };
681
-
682
- // Add AST nodes
683
- if (activity.astPaths.length > 0) {
684
- activity.astPaths.forEach(ast => {
685
- fileNode.children.push({
686
- name: ast.name,
687
- type: ast.type,
688
- path: `${filePath}#${ast.name}`,
689
- children: []
690
- });
691
- });
692
- }
693
-
694
- dirMap.get(workingDir).children.push(fileNode);
695
- }
696
-
697
- // Add directories to root
698
- root.children = Array.from(dirMap.values());
699
-
700
- // If only one directory and it's the root, flatten
701
- if (root.children.length === 1 && root.children[0].path === '/') {
702
- root.children = root.children[0].children;
703
- }
704
-
705
- this.treeData = root;
706
- }
707
-
708
- /**
709
- * Render the D3 tree
710
- */
711
- renderTree() {
712
- if (!this.treeData || !this.container) return;
713
-
714
- // Ensure SVG element exists
715
- const svgElement = document.getElementById('claude-activity-tree-svg');
716
- if (!svgElement) {
717
- console.warn('[CodeViewer] SVG element not found, skipping tree render');
718
- return;
719
- }
720
-
721
- const svg = d3.select(svgElement);
722
- if (svg.empty()) {
723
- console.warn('[CodeViewer] D3 could not select SVG element');
724
- return;
725
- }
726
-
727
- svg.selectAll('*').remove();
728
-
729
- // Get actual dimensions
730
- const svgContainer = document.getElementById('claude-tree-svg-container');
731
- if (svgContainer) {
732
- const rect = svgContainer.getBoundingClientRect();
733
- this.width = rect.width || 800;
734
- this.height = rect.height || 600;
735
- }
736
-
737
- // Create container group for zoom/pan
738
- const g = svg.append('g');
739
-
740
- // Setup zoom behavior
741
- const zoom = d3.zoom()
742
- .scaleExtent([0.1, 4])
743
- .on('zoom', (event) => {
744
- g.attr('transform', event.transform);
745
- });
746
-
747
- svg.call(zoom);
748
-
749
- // Create tree layout
750
- const treeLayout = d3.tree()
751
- .size([this.height - 100, this.width - 200]);
752
-
753
- // Create hierarchy
754
- this.d3Root = d3.hierarchy(this.treeData);
755
-
756
- // Apply tree layout
757
- treeLayout(this.d3Root);
758
-
759
- // Create links
760
- const link = g.selectAll('.link')
761
- .data(this.d3Root.links())
762
- .enter().append('path')
763
- .attr('class', 'link')
764
- .attr('d', d3.linkHorizontal()
765
- .x(d => d.y + 100)
766
- .y(d => d.x + 50))
767
- .style('fill', 'none')
768
- .style('stroke', '#ccc')
769
- .style('stroke-width', 1);
770
-
771
- // Create nodes
772
- const node = g.selectAll('.node')
773
- .data(this.d3Root.descendants())
774
- .enter().append('g')
775
- .attr('class', 'node')
776
- .attr('transform', d => `translate(${d.y + 100},${d.x + 50})`);
777
-
778
- // Add circles for nodes
779
- node.append('circle')
780
- .attr('r', this.nodeRadius)
781
- .style('fill', d => this.getNodeColor(d.data))
782
- .style('stroke', d => d.data.edited ? '#F44336' : '#999')
783
- .style('stroke-width', d => d.data.edited ? 2 : 1)
784
- .style('cursor', 'pointer')
785
- .on('click', (event, d) => this.handleNodeClick(event, d));
786
-
787
- // Add text labels
788
- node.append('text')
789
- .attr('dy', '.35em')
790
- .attr('x', d => d.children ? -10 : 10)
791
- .style('text-anchor', d => d.children ? 'end' : 'start')
792
- .style('font-size', '12px')
793
- .style('cursor', 'pointer')
794
- .text(d => d.data.name)
795
- .on('click', (event, d) => this.handleNodeClick(event, d));
796
-
797
- // Store tree reference
798
- this.d3Tree = { svg, g, zoom };
799
- }
800
-
801
- /**
802
- * Get node color based on type
803
- */
804
- getNodeColor(node) {
805
- switch (node.type) {
806
- case 'root': return '#666';
807
- case 'directory': return '#FFC107';
808
- case 'file': return '#4CAF50';
809
- case 'class': return '#2196F3';
810
- case 'function': return '#FF9800';
811
- case 'method': return '#9C27B0';
812
- default: return '#999';
813
- }
814
- }
815
-
816
- /**
817
- * Handle node click
818
- */
819
- handleNodeClick(event, d) {
820
- event.stopPropagation();
821
-
822
- // Toggle children
823
- if (d.children) {
824
- d._children = d.children;
825
- d.children = null;
826
- } else if (d._children) {
827
- d.children = d._children;
828
- d._children = null;
829
- }
830
-
831
- // Re-render tree
832
- this.renderTree();
833
-
834
- // Update selection
835
- this.selectedNode = d;
836
-
837
- // Update the data viewer in the left pane if it's a file
838
- if (d.data.type === 'file' && this.fileActivity.has(d.data.path)) {
839
- this.showFileDetails(d.data.path);
840
- }
841
- }
842
-
843
- /**
844
- * Show file details in the left viewer pane
845
- */
846
- showFileDetails(filePath) {
847
- const activity = this.fileActivity.get(filePath);
848
- if (!activity) return;
849
-
850
- const dataContent = document.getElementById('module-data-content');
851
- if (!dataContent) return;
852
-
853
- // Update header
854
- const dataHeader = document.querySelector('.module-data-header h5');
855
- if (dataHeader) {
856
- dataHeader.innerHTML = `📄 ${filePath.split('/').pop()}`;
857
- }
858
-
859
- // Build operations display
860
- let html = '<div style="padding: 10px; overflow-y: auto; height: 100%;">';
861
- html += `<div style="margin-bottom: 15px;">`;
862
- html += `<strong>File Path:</strong> ${filePath}<br>`;
863
- html += `<strong>Operations:</strong> ${activity.operations.length}<br>`;
864
- html += `<strong>Sessions:</strong> ${activity.sessions.size}`;
865
- html += `</div>`;
866
-
867
- // Show operations timeline
868
- html += '<div style="margin-bottom: 15px;"><strong>Operations Timeline:</strong></div>';
869
- activity.operations.forEach((op, index) => {
870
- const time = new Date(op.timestamp).toLocaleTimeString();
871
- html += `<div style="margin-bottom: 10px; padding: 8px; background: #f5f5f5; border-left: 3px solid ${this.getOperationColor(op.type)};">`;
872
- html += `<div><strong>${op.type}</strong> at ${time}</div>`;
873
-
874
- if (op.type === 'Edit' && op.parameters) {
875
- html += `<div style="margin-top: 5px; font-size: 0.9em;">`;
876
- html += `<div style="color: #d32f2f;">- ${this.escapeHtml(op.parameters.old_string || '').substring(0, 100)}</div>`;
877
- html += `<div style="color: #388e3c;">+ ${this.escapeHtml(op.parameters.new_string || '').substring(0, 100)}</div>`;
878
- html += `</div>`;
879
- }
880
- html += `</div>`;
881
- });
882
-
883
- // Show AST structure if available
884
- if (activity.astPaths.length > 0) {
885
- html += '<div style="margin-top: 15px;"><strong>AST Structure:</strong></div>';
886
- html += '<ul style="list-style: none; padding-left: 10px;">';
887
- activity.astPaths.forEach(ast => {
888
- const icon = ast.type === 'class' ? '🔷' : ast.type === 'function' ? '🔶' : '🔸';
889
- html += `<li>${icon} ${ast.name} (${ast.type})</li>`;
890
- });
891
- html += '</ul>';
892
- }
893
-
894
- html += '</div>';
895
- dataContent.innerHTML = html;
896
- }
897
-
898
- /**
899
- * Get operation color
900
- */
901
- getOperationColor(type) {
902
- switch (type) {
903
- case 'Write': return '#4CAF50';
904
- case 'Edit': return '#FF9800';
905
- case 'Read': return '#2196F3';
906
- default: return '#999';
907
- }
908
- }
909
-
910
- /**
911
- * Escape HTML
912
- */
913
- escapeHtml(text) {
914
- const div = document.createElement('div');
915
- div.textContent = text;
916
- return div.innerHTML;
917
- }
918
-
919
- /**
920
- * Expand all nodes
921
- */
922
- expandAllNodes() {
923
- if (!this.d3Root) return;
924
-
925
- this.d3Root.descendants().forEach(d => {
926
- if (d._children) {
927
- d.children = d._children;
928
- d._children = null;
929
- }
930
- });
931
-
932
- this.renderTree();
933
- }
934
-
935
- /**
936
- * Collapse all nodes
937
- */
938
- collapseAllNodes() {
939
- if (!this.d3Root) return;
940
-
941
- this.d3Root.descendants().forEach(d => {
942
- if (d.children && d.depth > 0) {
943
- d._children = d.children;
944
- d.children = null;
945
- }
946
- });
947
-
948
- this.renderTree();
949
- }
950
-
951
- /**
952
- * Reset zoom
953
- */
954
- resetZoom() {
955
- if (!this.d3Tree) return;
956
-
957
- this.d3Tree.svg.transition()
958
- .duration(750)
959
- .call(this.d3Tree.zoom.transform, d3.zoomIdentity);
960
- }
961
-
962
- /**
963
- * Update session list in main selector
964
- */
965
- updateSessionList() {
966
- // Update the main session selector if it exists
967
- const mainSelect = document.getElementById('session-select');
968
- if (!mainSelect) return;
969
-
970
- const currentValue = mainSelect.value;
971
-
972
- // Clear existing options except "All Sessions"
973
- while (mainSelect.options.length > 1) {
974
- mainSelect.remove(1);
975
- }
976
-
977
- // Add session options from our tracked sessions
978
- for (const [sessionId, session] of this.sessions.entries()) {
979
- // Check if option already exists
980
- let exists = false;
981
- for (let i = 0; i < mainSelect.options.length; i++) {
982
- if (mainSelect.options[i].value === sessionId) {
983
- exists = true;
984
- break;
985
- }
986
- }
987
-
988
- if (!exists) {
989
- const option = document.createElement('option');
990
- option.value = sessionId;
991
- option.textContent = `Session ${sessionId.substring(0, 8)}... (${session.files.size} files)`;
992
- mainSelect.appendChild(option);
993
- }
994
- }
995
-
996
- // Restore previous selection if it still exists
997
- if (currentValue) {
998
- mainSelect.value = currentValue;
999
- }
1000
- }
1001
-
1002
- /**
1003
- * Update statistics
1004
- */
1005
- updateStats() {
1006
- const stats = document.getElementById('claude-tree-stats');
1007
- if (!stats) return;
1008
-
1009
- const totalFiles = this.currentSession
1010
- ? Array.from(this.fileActivity.values()).filter(a => a.sessions.has(this.currentSession)).length
1011
- : this.fileActivity.size;
1012
-
1013
- const totalOps = this.currentSession
1014
- ? Array.from(this.fileActivity.values())
1015
- .filter(a => a.sessions.has(this.currentSession))
1016
- .reduce((sum, a) => sum + a.operations.length, 0)
1017
- : Array.from(this.fileActivity.values())
1018
- .reduce((sum, a) => sum + a.operations.length, 0);
1019
-
1020
- stats.textContent = `Files: ${totalFiles} | Operations: ${totalOps} | Sessions: ${this.sessions.size}`;
1021
- }
1022
- }
1023
-
1024
- // Create and export singleton instance
1025
- window.CodeViewer = new CodeViewer();
1026
-
1027
- // Auto-initialize when DOM is ready
1028
- if (document.readyState === 'loading') {
1029
- document.addEventListener('DOMContentLoaded', () => {
1030
- window.CodeViewer.initialize();
1031
-
1032
- // If Claude Tree tab is already active, show it
1033
- const claudeTreeTab = document.getElementById('claude-tree-tab');
1034
- if (claudeTreeTab && claudeTreeTab.classList.contains('active')) {
1035
- console.log('[CodeViewer] Claude Tree tab is active on load, showing tree...');
1036
- setTimeout(() => window.CodeViewer.show(), 100);
1037
- }
1038
- });
1039
- } else {
1040
- window.CodeViewer.initialize();
1041
-
1042
- // If Claude Tree tab is already active, show it
1043
- const claudeTreeTab = document.getElementById('claude-tree-tab');
1044
- if (claudeTreeTab && claudeTreeTab.classList.contains('active')) {
1045
- console.log('[CodeViewer] Claude Tree tab is active, showing tree...');
1046
- setTimeout(() => window.CodeViewer.show(), 100);
1047
- }
1048
- }
1049
-
1050
- // Also listen for tab changes to ensure we render when needed
1051
- document.addEventListener('tabChanged', (event) => {
1052
- if (event.detail && event.detail.newTab === 'claude-tree') {
1053
- console.log('[CodeViewer] Tab changed to Claude Tree, forcing show...');
1054
- setTimeout(() => window.CodeViewer.show(), 50);
1055
- }
1056
- });
1057
-
1058
- // ADDITIONAL: Listen for clicks on the Claude Tree tab button directly
1059
- document.addEventListener('click', (event) => {
1060
- if (event.target && event.target.matches('[data-tab="claude-tree"]')) {
1061
- console.log('[CodeViewer] Direct click on Claude Tree tab detected, forcing show...');
1062
- setTimeout(() => window.CodeViewer.show(), 100);
1063
- }
1064
- });
1065
-
1066
- // FALLBACK: Periodic check to ensure Claude Tree tab is properly rendered
1067
- setInterval(() => {
1068
- const claudeTreeTab = document.getElementById('claude-tree-tab');
1069
- const claudeTreeContainer = document.getElementById('claude-tree-container');
1070
-
1071
- if (claudeTreeTab && claudeTreeTab.classList.contains('active') &&
1072
- claudeTreeContainer && !claudeTreeContainer.querySelector('.activity-tree-wrapper')) {
1073
- console.log('[CodeViewer] Periodic check: Claude Tree tab is active but not properly rendered, fixing...');
1074
- window.CodeViewer.show();
1075
- }
1076
- }, 5000);
1
+ class e{constructor(){this.container=null,this.svg=null,this.initialized=!1,this.fileActivity=new Map,this.sessions=new Map,this.currentSession=null,this.treeData=null,this.d3Tree=null,this.d3Root=null,this.selectedNode=null,this.width=800,this.height=600,this.nodeRadius=5,this.renderInProgress=!1,this.containerObserver=null}initialize(){if(console.log("[CodeViewer] initialize() called"),this.initialized)console.log("[CodeViewer] Already initialized, skipping");else{console.log("[CodeViewer] Starting initialization...");try{this.setupContainer(),console.log("[CodeViewer] Container setup complete"),this.setupEventHandlers(),console.log("[CodeViewer] Event handlers setup complete"),this.subscribeToEvents(),console.log("[CodeViewer] Event subscription complete"),this.processExistingEvents(),console.log("[CodeViewer] Existing events processed"),this.initialized=!0,console.log("[CodeViewer] Initialization complete!")}catch(e){throw console.error("[CodeViewer] Error during initialization:",e),e}}}setupContainer(){const e=document.getElementById("claude-tree-container");e?(this.container=e,this.renderInterface()):console.error("File Tree container not found")}renderInterface(){if(!this.container)return void console.error("[CodeViewer] Container not found, cannot render interface");if(this.renderInProgress)return;const e=this.container.querySelector(".activity-tree-wrapper");this.container.querySelector(".file-tree-empty-state");const t=this.container.querySelector("#claude-activity-tree-svg");if(e&&t)return;this.renderInProgress=!0,this.containerObserver&&this.containerObserver.disconnect(),this.container.innerHTML="",this.container.innerHTML='\n <div class="activity-tree-wrapper" style="height: 100%; display: flex; flex-direction: column;">\n <div class="activity-controls" style="padding: 10px; border-bottom: 1px solid #ddd; background: #f9f9f9; display: flex; align-items: center; gap: 10px;">\n <button id="claude-expand-all-btn" class="control-btn" style="padding: 4px 8px; font-size: 0.9em;">Expand All</button>\n <button id="claude-collapse-all-btn" class="control-btn" style="padding: 4px 8px; font-size: 0.9em;">Collapse All</button>\n <button id="claude-reset-zoom-btn" class="control-btn" style="padding: 4px 8px; font-size: 0.9em;">Reset Zoom</button>\n <div class="stats" id="claude-tree-stats" style="margin-left: auto; font-size: 0.9em; color: #666;"></div>\n </div>\n <div class="tree-container" id="claude-tree-svg-container" style="flex: 1; overflow: hidden; position: relative; background: white;">\n <svg id="claude-activity-tree-svg" style="width: 100%; height: 100%;"></svg>\n </div>\n <div class="legend" style="padding: 5px 10px; border-top: 1px solid #ddd; background: #f9f9f9; font-size: 0.85em; display: flex; gap: 15px;">\n <span class="legend-item"><span style="color: #4CAF50;">●</span> File</span>\n <span class="legend-item"><span style="color: #2196F3;">●</span> Class</span>\n <span class="legend-item"><span style="color: #FF9800;">●</span> Function</span>\n <span class="legend-item"><span style="color: #9C27B0;">●</span> Method</span>\n <span class="legend-item"><span style="color: #F44336;">◆</span> Edited</span>\n <span class="legend-item"><span style="color: #4CAF50;">○</span> Viewed</span>\n </div>\n </div>\n ';const i=document.getElementById("claude-tree-svg-container");if(i){const e=i.getBoundingClientRect();this.width=e.width||800,this.height=e.height||600}this.renderInProgress=!1,this.containerObserver&&this.container&&this.containerObserver.observe(this.container,{childList:!0,subtree:!1})}renderContent(){this._showInternal()}show(){this._showInternal()}_showInternal(){console.log("[CodeViewer] _showInternal() called");const e=document.getElementById("claude-tree-container");if(!e)return void console.error("[CodeViewer] File Tree container not found!");console.log("[CodeViewer] Found container, current HTML length:",e.innerHTML.length),console.log("[CodeViewer] Container children:",e.children.length),this.refreshFromFileToolTracker();let t=!1;if(["#events-list",".events-list",".event-item",".no-events",'[id*="event"]','[class*="event"]'].forEach(i=>{const s=e.querySelectorAll(i);s.length>0&&(console.warn(`[CodeViewer] Found ${s.length} foreign elements matching '${i}', removing...`),s.forEach(e=>e.remove()),t=!0)}),t&&(console.warn("[CodeViewer] Foreign content removed, clearing container completely for fresh start"),e.innerHTML=""),e.setAttribute("data-owner","code-viewer"),e.setAttribute("data-tab-reserved","claude-tree"),e.setAttribute("data-component","CodeViewer"),this.container&&this.container===e||(this.container=e),this.initialized){const e=this.container.querySelector(".activity-tree-wrapper"),t=this.container.querySelector(".file-tree-empty-state");e||t||this.renderInterface()}else this.initialize();this.containerObserver||this.protectContainer(),this.setupControlHandlers();const i=document.getElementById("session-select");i&&(this.currentSession=i.value||null),this.buildTreeData(),this.renderTree(),this.updateStats()}protectContainer(){const e=document.getElementById("claude-tree-container");if(!e)return;this.containerObserver&&this.containerObserver.disconnect();let t=!1;this.containerObserver=new MutationObserver(i=>{for(const n of i){for(const i of n.addedNodes)if(i.nodeType===Node.ELEMENT_NODE){const n=i;if(n.classList?.contains("event-item")||n.classList?.contains("events-list")||n.classList?.contains("no-events")||"events-list"===n.id||"agents-list"===n.id||"tools-list"===n.id||"files-list"===n.id||n.textContent&&(n.textContent.includes("[hook]")||n.textContent.includes("hook.user_prompt")||n.textContent.includes("hook.pre_tool")||n.textContent.includes("hook.post_tool")||n.textContent.includes("Connect to Socket.IO")||n.textContent.includes("No events")||n.textContent.includes("No agent events")||n.textContent.includes("No tool events")||n.textContent.includes("No file operations"))||"DIV"===n.tagName&&!n.classList?.contains("activity-tree-wrapper")&&!n.classList?.contains("file-tree-empty-state")&&!n.classList?.contains("activity-controls")&&!n.classList?.contains("tree-container")&&!n.classList?.contains("legend")&&!n.classList?.contains("stats")&&!n.id?.startsWith("claude-")){try{i.remove()}catch(s){console.warn("[CodeViewer] Failed to remove unwanted node:",s)}t||this.renderInProgress||(t=!0,setTimeout(()=>{t=!1,e.querySelector(".activity-tree-wrapper")||e.querySelector(".file-tree-empty-state")||(this.renderInterface(),this.setupControlHandlers(),this.buildTreeData(),this.renderTree())},50))}}if("childList"===n.type&&n.removedNodes.length>0)for(const e of n.removedNodes)if(e.nodeType===Node.ELEMENT_NODE){const i=e;(i.classList?.contains("activity-tree-wrapper")||i.classList?.contains("file-tree-empty-state"))&&(t||this.renderInProgress||(t=!0,setTimeout(()=>{t=!1,this.renderInterface(),this.setupControlHandlers(),this.buildTreeData(),this.renderTree()},50)))}}}),this.containerObserver.observe(e,{childList:!0,subtree:!1})}setupControlHandlers(){const e=document.getElementById("session-select");e&&!e.hasAttribute("data-tree-listener")&&(e.setAttribute("data-tree-listener","true"),e.addEventListener("change",e=>{this.currentSession=e.target.value||null,this.isTabActive()&&(this.buildTreeData(),this.renderTree(),this.updateStats())}));const t=document.getElementById("claude-expand-all-btn");t&&!t.hasAttribute("data-listener")&&(t.setAttribute("data-listener","true"),t.addEventListener("click",()=>{this.expandAllNodes()}));const i=document.getElementById("claude-collapse-all-btn");i&&!i.hasAttribute("data-listener")&&(i.setAttribute("data-listener","true"),i.addEventListener("click",()=>{this.collapseAllNodes()}));const s=document.getElementById("claude-reset-zoom-btn");s&&!s.hasAttribute("data-listener")&&(s.setAttribute("data-listener","true"),s.addEventListener("click",()=>{this.resetZoom()}))}setupEventHandlers(){}subscribeToEvents(){window.socket&&(window.socket.on("claude_event",e=>{(this.isFileOperationEvent(e)||this.isDirectFileEvent(e))&&setTimeout(()=>{this.refreshFromFileToolTracker(),this.isTabActive()&&(this.buildTreeData(),this.renderTree(),this.updateStats())},100)}),window.socket.on("file:read",e=>{this.handleDirectFileEvent("Read",e)}),window.socket.on("file:write",e=>{this.handleDirectFileEvent("Write",e)}),window.socket.on("file:edit",e=>{this.handleDirectFileEvent("Edit",e)})),window.eventBus&&window.eventBus.on("claude_event",e=>{(this.isFileOperationEvent(e)||this.isDirectFileEvent(e))&&(this.processClaudeEvent(e),this.isTabActive()&&(this.buildTreeData(),this.renderTree(),this.updateStats()))})}isTabActive(){const e=document.getElementById("claude-tree-tab");return e&&e.classList.contains("active")}processExistingEvents(){if(console.log("[CodeViewer] processExistingEvents called"),window.dashboard&&window.dashboard.fileToolTracker)this.refreshFromFileToolTracker();else if(window.dashboard&&window.dashboard.eventStore){const e=window.dashboard.eventStore.getAllEvents();console.log("[CodeViewer] Fallback to eventStore, total events:",e.length);let t=0,i=0;e.forEach(e=>{"hook"===e.type&&console.log("[CodeViewer] Hook event:",{subtype:e.subtype,tool_name:e.data?.tool_name,timestamp:e.timestamp}),this.isFileOperationEvent(e)&&(t++,console.log("[CodeViewer] Found file operation event:",e),this.processClaudeEvent(e),i++)}),console.log("[CodeViewer] processExistingEvents summary:",{totalEvents:e.length,fileOperations:t,processed:i,currentFileActivitySize:this.fileActivity.size})}else console.log("[CodeViewer] No dashboard or eventStore available")}isFileOperationEvent(e){if("hook"===e.type&&("pre_tool"===e.subtype||"post_tool"===e.subtype)&&e.data&&e.data.tool_name){return["Read","Write","Edit","MultiEdit","NotebookEdit"].includes(e.data.tool_name)}return!1}isDirectFileEvent(e){return!!("file_operation"===e.type||e.tool&&["Read","Write","Edit","MultiEdit","NotebookEdit"].includes(e.tool))}handleDirectFileEvent(e,t){const i={type:"file_operation",tool:e,data:{tool_name:e,tool_parameters:t.parameters||t,tool_output:t.output||null,session_id:t.session_id||this.currentSession,working_directory:t.working_directory||"/"},timestamp:t.timestamp||(new Date).toISOString()};this.processClaudeEvent(i),this.isTabActive()&&(this.buildTreeData(),this.renderTree(),this.updateStats())}isFileOperation(e){return["Read","Write","Edit","MultiEdit","NotebookEdit"].includes(e.tool_name)}processClaudeEvent(e){if(!this.isFileOperationEvent(e)&&!this.isDirectFileEvent(e))return;let t,i,s,n,o,r,a;if(this.isFileOperationEvent(e)){const a=e.data||{};t=a.tool_name,i=a.tool_parameters||{},s=a.tool_output,n=e.timestamp||(new Date).toISOString(),o=e.session_id||a.session_id,r=a.working_directory||"/"}else if(this.isDirectFileEvent(e)){const a=e.data||e;t=e.tool||a.tool_name,i=a.tool_parameters||a.parameters||{},s=a.tool_output||a.output,n=e.timestamp||a.timestamp||(new Date).toISOString(),o=e.session_id||a.session_id,r=a.working_directory||"/"}a=i.file_path||i.notebook_path,this.processFileOperation({tool_name:t,tool_parameters:i,tool_output:s,timestamp:n,session_id:o,working_directory:r,filePath:a})}processEvent(e){if(!this.isFileOperation(e))return;const{tool_name:t,tool_parameters:i,tool_output:s,timestamp:n,session_id:o,working_directory:r}=e,a=i?.file_path||i?.notebook_path;this.processFileOperation({tool_name:t,tool_parameters:i,tool_output:s,timestamp:n,session_id:o,working_directory:r,filePath:a})}processFileOperation({tool_name:e,tool_parameters:t,tool_output:i,timestamp:s,session_id:n,working_directory:o,filePath:r}){if(!r)return;n&&!this.sessions.has(n)&&(this.sessions.set(n,{id:n,working_directory:o||"/",files:new Set}),this.updateSessionList()),this.fileActivity.has(r)||this.fileActivity.set(r,{path:r,operations:[],sessions:new Set,working_directories:new Set,lastContent:null,astPaths:[]});const a=this.fileActivity.get(r);if(a.operations.push({type:e,timestamp:s,parameters:t,output:i,session_id:n}),n){a.sessions.add(n);const e=this.sessions.get(n);e&&e.files.add(r)}if(o&&a.working_directories.add(o),"Write"===e&&t.content)a.lastContent=t.content,a.astPaths=this.extractASTPaths(t.content,r);else if("Read"===e&&i?.content)a.lastContent=i.content,a.astPaths=this.extractASTPaths(i.content,r);else if("Edit"===e&&a.lastContent){const e=t.old_string,i=t.new_string;e&&i&&(a.lastContent=a.lastContent.replace(e,i),a.astPaths=this.extractASTPaths(a.lastContent,r))}}extractASTPaths(e,t){if(!e||"string"!=typeof e)return[];const i=t.split(".").pop()?.toLowerCase(),s=[];if("py"===i){const t=/^class\s+(\w+)/gm,i=/^def\s+(\w+)/gm,n=/^\s{4,}def\s+(\w+)/gm;let o;for(;null!==(o=t.exec(e));)s.push({name:o[1],type:"class"});for(;null!==(o=i.exec(e));)s.push({name:o[1],type:"function"});for(;null!==(o=n.exec(e));)s.some(e=>e.name===o[1])||s.push({name:o[1],type:"method"})}else if("js"===i||"jsx"===i||"ts"===i||"tsx"===i){const t=/class\s+(\w+)/g,i=/function\s+(\w+)/g,n=/const\s+(\w+)\s*=\s*\([^)]*\)\s*=>/g;let o;for(;null!==(o=t.exec(e));)s.push({name:o[1],type:"class"});for(;null!==(o=i.exec(e));)s.push({name:o[1],type:"function"});for(;null!==(o=n.exec(e));)s.push({name:o[1],type:"function"})}return s}buildTreeData(){const e={name:`Session: ${(this.currentSession||"current-session").substring(0,8)+"..."}`,type:"root",children:[]};if(!this.fileActivity||0===this.fileActivity.size)return e.children.push({name:"(No file operations yet)",type:"placeholder",children:[]}),this.treeData=e,void console.log("[CodeViewer] Built minimal tree with session root");const t=new Map;for(const[i,s]of this.fileActivity.entries()){if(this.currentSession&&!s.sessions.has(this.currentSession))continue;const e=Array.from(s.working_directories)[0]||"/";t.has(e)||t.set(e,{name:e.split("/").pop()||e,path:e,type:"directory",children:[]});const n=i.split("/").pop(),o=s.operations.some(e=>"Edit"===e.type||"Write"===e.type),r={name:n,path:i,type:"file",edited:o,operations:s.operations.length,children:[]};s.astPaths.length>0&&s.astPaths.forEach(e=>{r.children.push({name:e.name,type:e.type,path:`${i}#${e.name}`,children:[]})}),t.get(e).children.push(r)}e.children=Array.from(t.values()),1===e.children.length&&"/"===e.children[0].path&&(e.children=e.children[0].children),this.treeData=e}renderTree(){if(!this.treeData||!this.container)return;const e=document.getElementById("claude-activity-tree-svg");if(!e)return void console.warn("[CodeViewer] SVG element not found, skipping tree render");const t=d3.select(e);if(t.empty())return void console.warn("[CodeViewer] D3 could not select SVG element");t.selectAll("*").remove();const i=document.getElementById("claude-tree-svg-container");if(i){const e=i.getBoundingClientRect();this.width=e.width||800,this.height=e.height||600}const s=t.append("g"),n=d3.zoom().scaleExtent([.1,4]).on("zoom",e=>{s.attr("transform",e.transform)});t.call(n);const o=d3.tree().size([this.height-100,this.width-200]);this.d3Root=d3.hierarchy(this.treeData),o(this.d3Root),s.selectAll(".link").data(this.d3Root.links()).enter().append("path").attr("class","link").attr("d",d3.linkHorizontal().x(e=>e.y+100).y(e=>e.x+50)).style("fill","none").style("stroke","#ccc").style("stroke-width",1);const r=s.selectAll(".node").data(this.d3Root.descendants()).enter().append("g").attr("class","node").attr("transform",e=>`translate(${e.y+100},${e.x+50})`);r.append("circle").attr("r",this.nodeRadius).style("fill",e=>this.getNodeColor(e.data)).style("stroke",e=>e.data.edited?"#F44336":"#999").style("stroke-width",e=>e.data.edited?2:1).style("cursor","pointer").on("click",(e,t)=>this.handleNodeClick(e,t)),r.append("text").attr("dy",".35em").attr("x",e=>e.children?-10:10).style("text-anchor",e=>e.children?"end":"start").style("font-size","12px").style("cursor","pointer").text(e=>e.data.name).on("click",(e,t)=>this.handleNodeClick(e,t)),this.d3Tree={svg:t,g:s,zoom:n}}getNodeColor(e){switch(e.type){case"root":return"#666";case"directory":return"#FFC107";case"file":return"#4CAF50";case"class":return"#2196F3";case"function":return"#FF9800";case"method":return"#9C27B0";default:return"#999"}}handleNodeClick(e,t){e.stopPropagation(),t.children?(t._children=t.children,t.children=null):t._children&&(t.children=t._children,t._children=null),this.renderTree(),this.selectedNode=t,"file"===t.data.type&&this.fileActivity.has(t.data.path)&&this.showFileDetails(t.data.path)}showFileDetails(e){const t=this.fileActivity.get(e);if(!t)return;const i=document.getElementById("module-data-content");if(!i)return;const s=document.querySelector(".module-data-header h5");s&&(s.innerHTML=`📄 ${e.split("/").pop()}`);let n='<div style="padding: 10px; overflow-y: auto; height: 100%;">';n+='<div style="margin-bottom: 15px;">',n+=`<strong>File Path:</strong> ${e}<br>`,n+=`<strong>Operations:</strong> ${t.operations.length}<br>`,n+=`<strong>Sessions:</strong> ${t.sessions.size}`,n+="</div>",n+='<div style="margin-bottom: 15px;"><strong>Operations Timeline:</strong></div>',t.operations.forEach((e,t)=>{const i=new Date(e.timestamp).toLocaleTimeString();n+=`<div style="margin-bottom: 10px; padding: 8px; background: #f5f5f5; border-left: 3px solid ${this.getOperationColor(e.type)};">`,n+=`<div><strong>${e.type}</strong> at ${i}</div>`,"Edit"===e.type&&e.parameters&&(n+='<div style="margin-top: 5px; font-size: 0.9em;">',n+=`<div style="color: #d32f2f;">- ${this.escapeHtml(e.parameters.old_string||"").substring(0,100)}</div>`,n+=`<div style="color: #388e3c;">+ ${this.escapeHtml(e.parameters.new_string||"").substring(0,100)}</div>`,n+="</div>"),n+="</div>"}),t.astPaths.length>0&&(n+='<div style="margin-top: 15px;"><strong>AST Structure:</strong></div>',n+='<ul style="list-style: none; padding-left: 10px;">',t.astPaths.forEach(e=>{const t="class"===e.type?"🔷":"function"===e.type?"🔶":"🔸";n+=`<li>${t} ${e.name} (${e.type})</li>`}),n+="</ul>"),n+="</div>",i.innerHTML=n}getOperationColor(e){switch(e){case"Write":return"#4CAF50";case"Edit":return"#FF9800";case"Read":return"#2196F3";default:return"#999"}}escapeHtml(e){const t=document.createElement("div");return t.textContent=e,t.innerHTML}expandAllNodes(){this.d3Root&&(this.d3Root.descendants().forEach(e=>{e._children&&(e.children=e._children,e._children=null)}),this.renderTree())}collapseAllNodes(){this.d3Root&&(this.d3Root.descendants().forEach(e=>{e.children&&e.depth>0&&(e._children=e.children,e.children=null)}),this.renderTree())}resetZoom(){this.d3Tree&&this.d3Tree.svg.transition().duration(750).call(this.d3Tree.zoom.transform,d3.zoomIdentity)}updateSessionList(){const e=document.getElementById("session-select");if(!e)return;const t=e.value;for(;e.options.length>1;)e.remove(1);for(const[i,s]of this.sessions.entries()){let t=!1;for(let s=0;s<e.options.length;s++)if(e.options[s].value===i){t=!0;break}if(!t){const t=document.createElement("option");t.value=i,t.textContent=`Session ${i.substring(0,8)}... (${s.files.size} files)`,e.appendChild(t)}}t&&(e.value=t)}updateStats(){const e=document.getElementById("claude-tree-stats");if(!e)return;const t=this.currentSession?Array.from(this.fileActivity.values()).filter(e=>e.sessions.has(this.currentSession)).length:this.fileActivity.size,i=this.currentSession?Array.from(this.fileActivity.values()).filter(e=>e.sessions.has(this.currentSession)).reduce((e,t)=>e+t.operations.length,0):Array.from(this.fileActivity.values()).reduce((e,t)=>e+t.operations.length,0);e.textContent=`Files: ${t} | Operations: ${i} | Sessions: ${this.sessions.size}`}refreshFromFileToolTracker(){if(!window.dashboard||!window.dashboard.fileToolTracker)return void console.log("[CodeViewer] FileToolTracker not available");const e=window.dashboard.fileToolTracker.getFileOperations();console.log("[CodeViewer] Refreshing from FileToolTracker:",e.size,"files"),this.fileActivity.clear(),this.sessions.clear();const t="current-session";this.sessions.set(t,{id:t,startTime:(new Date).toISOString(),files:new Set}),this.currentSession=t,e.forEach((e,i)=>{this.sessions.get(t).files.add(i);const s=e.operations[0];e.operations[e.operations.length-1],this.fileActivity.set(i,{path:i,firstAccess:s?s.timestamp:e.lastOperation,lastAccess:e.lastOperation,accessCount:e.operations.length,operations:e.operations.map(e=>({type:e.operation,timestamp:e.timestamp,agent:e.agent})),workingDirectory:s?s.workingDirectory:null,astNodes:[],content:null})}),console.log("[CodeViewer] File activity refreshed:",this.fileActivity.size,"files")}}try{window.CodeViewer=new e,console.log("[CodeViewer] Instance created successfully")}catch(t){console.error("[CodeViewer] FAILED TO CREATE INSTANCE:",t)}if("loading"===document.readyState)document.addEventListener("DOMContentLoaded",()=>{console.log("[CodeViewer] DOMContentLoaded - attempting initialization");try{window.CodeViewer.initialize();const e=document.getElementById("claude-tree-tab");e&&e.classList.contains("active")&&setTimeout(()=>window.CodeViewer.show(),100)}catch(t){console.error("[CodeViewer] FAILED TO INITIALIZE:",t)}});else{console.log("[CodeViewer] DOM already loaded - initializing immediately");try{window.CodeViewer.initialize();const e=document.getElementById("claude-tree-tab");e&&e.classList.contains("active")&&(console.log("[CodeViewer] File Tree tab is active, showing in 100ms"),setTimeout(()=>window.CodeViewer.show(),100))}catch(t){console.error("[CodeViewer] FAILED TO INITIALIZE:",t)}}document.addEventListener("tabChanged",e=>{e.detail&&"claude-tree"===e.detail.newTab&&setTimeout(()=>window.CodeViewer.show(),50)}),setInterval(()=>{const e=document.getElementById("claude-tree-tab"),t=document.getElementById("claude-tree-container");e&&e.classList.contains("active")&&t&&!t.querySelector(".activity-tree-wrapper")&&!t.querySelector(".file-tree-empty-state")&&window.CodeViewer.show()},5e3);
2
+ //# sourceMappingURL=code-viewer.js.map