claude-mpm 4.2.39__py3-none-any.whl → 4.2.42__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 (39) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_ENGINEER.md +114 -1
  3. claude_mpm/agents/BASE_OPS.md +156 -1
  4. claude_mpm/agents/INSTRUCTIONS.md +120 -11
  5. claude_mpm/agents/WORKFLOW.md +160 -10
  6. claude_mpm/agents/templates/agentic-coder-optimizer.json +17 -12
  7. claude_mpm/agents/templates/react_engineer.json +217 -0
  8. claude_mpm/agents/templates/web_qa.json +40 -4
  9. claude_mpm/cli/__init__.py +3 -5
  10. claude_mpm/commands/mpm-browser-monitor.md +370 -0
  11. claude_mpm/commands/mpm-monitor.md +177 -0
  12. claude_mpm/dashboard/static/built/components/code-viewer.js +1076 -2
  13. claude_mpm/dashboard/static/built/components/ui-state-manager.js +465 -2
  14. claude_mpm/dashboard/static/css/dashboard.css +2 -0
  15. claude_mpm/dashboard/static/js/browser-console-monitor.js +495 -0
  16. claude_mpm/dashboard/static/js/components/browser-log-viewer.js +763 -0
  17. claude_mpm/dashboard/static/js/components/code-viewer.js +931 -340
  18. claude_mpm/dashboard/static/js/components/diff-viewer.js +891 -0
  19. claude_mpm/dashboard/static/js/components/file-change-tracker.js +443 -0
  20. claude_mpm/dashboard/static/js/components/file-change-viewer.js +690 -0
  21. claude_mpm/dashboard/static/js/components/ui-state-manager.js +307 -19
  22. claude_mpm/dashboard/static/js/socket-client.js +2 -2
  23. claude_mpm/dashboard/static/test-browser-monitor.html +470 -0
  24. claude_mpm/dashboard/templates/index.html +62 -99
  25. claude_mpm/services/cli/unified_dashboard_manager.py +1 -1
  26. claude_mpm/services/monitor/daemon.py +69 -36
  27. claude_mpm/services/monitor/daemon_manager.py +186 -29
  28. claude_mpm/services/monitor/handlers/browser.py +451 -0
  29. claude_mpm/services/monitor/server.py +272 -5
  30. {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/METADATA +1 -1
  31. {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/RECORD +35 -29
  32. claude_mpm/agents/templates/agentic-coder-optimizer.md +0 -44
  33. claude_mpm/agents/templates/agentic_coder_optimizer.json +0 -238
  34. claude_mpm/agents/templates/test-non-mpm.json +0 -20
  35. claude_mpm/dashboard/static/dist/components/code-viewer.js +0 -2
  36. {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/WHEEL +0 -0
  37. {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/entry_points.txt +0 -0
  38. {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/licenses/LICENSE +0 -0
  39. {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/top_level.txt +0 -0
@@ -1,17 +1,29 @@
1
1
  /**
2
- * Code Viewer Component
2
+ * Code Viewer Component - File Activity Tree Viewer
3
3
  *
4
- * Modal window for displaying source code with syntax highlighting.
5
- * Supports navigation between parent/child nodes and shows code metrics.
4
+ * Shows a D3.js tree visualization of files that have been 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 File Tree tab of the dashboard.
6
8
  */
7
9
 
8
10
  class CodeViewer {
9
11
  constructor() {
10
- this.modal = null;
11
- this.currentNode = null;
12
- this.socket = null;
12
+ this.container = null;
13
+ this.svg = null;
13
14
  this.initialized = false;
14
- this.codeCache = new Map();
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;
15
27
  }
16
28
 
17
29
  /**
@@ -19,462 +31,1041 @@ class CodeViewer {
19
31
  */
20
32
  initialize() {
21
33
  if (this.initialized) {
34
+ console.log('[CodeViewer] Already initialized, skipping');
22
35
  return;
23
36
  }
24
37
 
25
- this.createModal();
38
+ console.log('[CodeViewer] Initializing...');
39
+ this.setupContainer();
26
40
  this.setupEventHandlers();
27
41
  this.subscribeToEvents();
42
+ this.processExistingEvents();
28
43
 
29
44
  this.initialized = true;
30
- console.log('Code viewer initialized');
45
+ console.log('[CodeViewer] Code Viewer (File Activity Tree) initialized successfully');
31
46
  }
32
47
 
33
48
  /**
34
- * Create modal DOM structure
49
+ * Setup the container in the File Tree tab
35
50
  */
36
- createModal() {
37
- const modalHtml = `
38
- <div class="code-viewer-modal" id="code-viewer-modal">
39
- <div class="code-viewer-content">
40
- <div class="code-viewer-header">
41
- <div class="code-viewer-title" id="code-viewer-title">
42
- Loading...
43
- </div>
44
- <div class="code-viewer-info">
45
- <span id="code-viewer-type">Type: --</span>
46
- <span id="code-viewer-lines">Lines: --</span>
47
- <span id="code-viewer-complexity">Complexity: --</span>
48
- </div>
49
- <button class="code-viewer-close" id="code-viewer-close">×</button>
50
- </div>
51
- <div class="code-viewer-body">
52
- <pre class="code-viewer-code line-numbers" id="code-viewer-code">
53
- <code class="language-python" id="code-viewer-code-content"></code>
54
- </pre>
55
- </div>
56
- <div class="code-viewer-navigation">
57
- <div class="nav-group">
58
- <button class="code-nav-button" id="code-nav-parent" disabled>
59
- ⬆️ Parent
60
- </button>
61
- <button class="code-nav-button" id="code-nav-prev" disabled>
62
- ⬅️ Previous
63
- </button>
64
- <button class="code-nav-button" id="code-nav-next" disabled>
65
- ➡️ Next
66
- </button>
67
- </div>
68
- <div class="nav-info">
69
- <span id="code-nav-position">-- / --</span>
70
- </div>
71
- <div class="nav-actions">
72
- <button class="code-nav-button" id="code-copy">
73
- 📋 Copy
74
- </button>
75
- <button class="code-nav-button" id="code-open-file">
76
- 📂 Open File
77
- </button>
78
- </div>
79
- </div>
51
+ setupContainer() {
52
+ // Find the File Tree tab container
53
+ const treeContainer = document.getElementById('claude-tree-container');
54
+ if (!treeContainer) {
55
+ console.error('File 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 File 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>
80
119
  </div>
81
120
  </div>
82
121
  `;
83
122
 
84
- // Add modal to body
85
- document.body.insertAdjacentHTML('beforeend', modalHtml);
86
- this.modal = document.getElementById('code-viewer-modal');
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
+ }
87
141
  }
88
142
 
89
143
  /**
90
- * Setup event handlers
144
+ * Render the content without switching tabs
145
+ * This is called by UIStateManager when the tab is already active
91
146
  */
92
- setupEventHandlers() {
93
- // Close button
94
- document.getElementById('code-viewer-close').addEventListener('click', () => {
95
- this.hide();
96
- });
147
+ renderContent() {
148
+ console.log('[CodeViewer] renderContent() called');
149
+ this._showInternal();
150
+ }
151
+
152
+ /**
153
+ * Show the activity tree (for backward compatibility)
154
+ * Note: Tab switching is now handled by UIStateManager
155
+ */
156
+ show() {
157
+ console.log('[CodeViewer] show() called');
158
+ this._showInternal();
159
+ }
160
+
161
+ /**
162
+ * Internal show implementation (without tab switching)
163
+ */
164
+ _showInternal() {
165
+
166
+ // Get the file tree container
167
+ const claudeTreeContainer = document.getElementById('claude-tree-container');
168
+ if (!claudeTreeContainer) {
169
+ console.error('[CodeViewer] File Tree container not found!');
170
+ return;
171
+ }
97
172
 
98
- // Close on backdrop click
99
- this.modal.addEventListener('click', (e) => {
100
- if (e.target === this.modal) {
101
- this.hide();
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();
102
193
  }
103
- });
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
+ }
104
219
 
105
- // Close on ESC key
106
- document.addEventListener('keydown', (e) => {
107
- if (e.key === 'Escape' && this.modal.classList.contains('show')) {
108
- this.hide();
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 File 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
+ }
109
317
  }
110
318
  });
111
-
112
- // Navigation buttons
113
- document.getElementById('code-nav-parent').addEventListener('click', () => {
114
- this.navigateToParent();
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
115
324
  });
325
+
326
+ console.log('[CodeViewer] Container protection enabled with aggressive filtering');
327
+ }
116
328
 
117
- document.getElementById('code-nav-prev').addEventListener('click', () => {
118
- this.navigateToPrevious();
119
- });
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
+ }
120
347
 
121
- document.getElementById('code-nav-next').addEventListener('click', () => {
122
- this.navigateToNext();
123
- });
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
+ }
124
356
 
125
- // Action buttons
126
- document.getElementById('code-copy').addEventListener('click', () => {
127
- this.copyCode();
128
- });
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
+ }
129
365
 
130
- document.getElementById('code-open-file').addEventListener('click', () => {
131
- this.openInEditor();
132
- });
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
133
381
  }
134
382
 
135
383
  /**
136
- * Subscribe to Socket.IO events
384
+ * Subscribe to events from socket and event bus
137
385
  */
138
386
  subscribeToEvents() {
387
+ // Listen for claude events from socket
139
388
  if (window.socket) {
140
- this.socket = window.socket;
141
-
142
- // Listen for code content responses
143
- this.socket.on('code:content:response', (data) => {
144
- this.handleCodeContent(data);
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 File 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 File Tree tab is active
410
+ if (this.isTabActive()) {
411
+ this.buildTreeData();
412
+ this.renderTree();
413
+ this.updateStats();
414
+ }
415
+ }
145
416
  });
146
417
  }
147
418
  }
148
419
 
149
420
  /**
150
- * Show the code viewer with node data
421
+ * Check if File Tree tab is active
151
422
  */
152
- show(nodeData) {
153
- if (!this.initialized) {
154
- this.initialize();
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);
155
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;
156
469
 
157
- this.currentNode = nodeData;
158
- this.modal.classList.add('show');
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 || '/';
159
478
 
160
- // Update header
161
- this.updateHeader(nodeData);
479
+ const filePath = tool_parameters.file_path || tool_parameters.notebook_path;
162
480
 
163
- // Load code content
164
- this.loadCode(nodeData);
481
+ console.log('[CodeViewer] Processing file operation:', tool_name, filePath);
165
482
 
166
- // Update navigation
167
- this.updateNavigation(nodeData);
483
+ this.processFileOperation({
484
+ tool_name,
485
+ tool_parameters,
486
+ tool_output,
487
+ timestamp,
488
+ session_id,
489
+ working_directory,
490
+ filePath
491
+ });
168
492
  }
169
493
 
170
494
  /**
171
- * Hide the code viewer
495
+ * Process a file operation event (legacy format)
172
496
  */
173
- hide() {
174
- this.modal.classList.remove('show');
175
- this.currentNode = null;
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
+ });
176
512
  }
177
513
 
178
514
  /**
179
- * Update modal header
515
+ * Process a file operation
180
516
  */
181
- updateHeader(nodeData) {
182
- // Update title
183
- const title = document.getElementById('code-viewer-title');
184
- title.textContent = `${nodeData.name} (${nodeData.path || 'Unknown'})`;
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);
185
544
 
186
- // Update info
187
- document.getElementById('code-viewer-type').textContent = `Type: ${nodeData.type}`;
188
- document.getElementById('code-viewer-lines').textContent = `Lines: ${nodeData.lines || '--'}`;
189
- document.getElementById('code-viewer-complexity').textContent = `Complexity: ${nodeData.complexity || '--'}`;
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)
190
584
  }
191
585
 
192
586
  /**
193
- * Load code content
587
+ * Extract AST paths from code content
194
588
  */
195
- loadCode(nodeData) {
196
- const codeContent = document.getElementById('code-viewer-code-content');
589
+ extractASTPaths(content, filePath) {
590
+ if (!content || typeof content !== 'string') return [];
197
591
 
198
- // Check cache first
199
- const cacheKey = `${nodeData.path}:${nodeData.line}`;
200
- if (this.codeCache.has(cacheKey)) {
201
- this.displayCode(this.codeCache.get(cacheKey));
202
- return;
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
+ }
203
630
  }
631
+
632
+ return paths;
633
+ }
634
+
635
+ /**
636
+ * Build tree data from file activity
637
+ */
638
+ buildTreeData() {
639
+ const root = {
640
+ name: 'File 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());
204
699
 
205
- // Show loading state
206
- codeContent.textContent = 'Loading code...';
207
-
208
- // Request code from server
209
- if (this.socket) {
210
- this.socket.emit('code:content:request', {
211
- path: nodeData.path,
212
- line: nodeData.line,
213
- type: nodeData.type,
214
- name: nodeData.name
215
- });
216
- } else {
217
- // Fallback: show mock code for demo
218
- this.displayMockCode(nodeData);
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;
219
703
  }
704
+
705
+ this.treeData = root;
220
706
  }
221
707
 
222
708
  /**
223
- * Handle code content response
709
+ * Render the D3 tree
224
710
  */
225
- handleCodeContent(data) {
226
- if (!data.success) {
227
- this.displayError(data.error || 'Failed to load code');
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');
228
724
  return;
229
725
  }
230
726
 
231
- // Cache the content
232
- const cacheKey = `${data.path}:${data.line}`;
233
- this.codeCache.set(cacheKey, data.content);
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);
234
755
 
235
- // Display the code
236
- this.displayCode(data.content);
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 };
237
799
  }
238
800
 
239
801
  /**
240
- * Display code with syntax highlighting
802
+ * Get node color based on type
241
803
  */
242
- displayCode(code) {
243
- const codeContent = document.getElementById('code-viewer-code-content');
244
- const codeElement = document.getElementById('code-viewer-code');
245
-
246
- // Set the code content
247
- codeContent.textContent = code;
248
-
249
- // Update language class based on file extension
250
- const language = this.detectLanguage(this.currentNode.path);
251
- codeContent.className = `language-${language}`;
252
-
253
- // Apply Prism syntax highlighting
254
- if (window.Prism) {
255
- Prism.highlightElement(codeContent);
256
-
257
- // Add line numbers if plugin is available
258
- if (Prism.plugins && Prism.plugins.lineNumbers) {
259
- Prism.plugins.lineNumbers.resize(codeElement);
260
- }
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';
261
813
  }
262
814
  }
263
815
 
264
816
  /**
265
- * Display mock code for demo purposes
817
+ * Handle node click
266
818
  */
267
- displayMockCode(nodeData) {
268
- let mockCode = '';
269
-
270
- switch (nodeData.type) {
271
- case 'class':
272
- mockCode = `class ${nodeData.name}:
273
- """
274
- ${nodeData.docstring || 'A sample class implementation.'}
275
- """
276
-
277
- def __init__(self):
278
- """Initialize the ${nodeData.name} class."""
279
- self._data = {}
280
- self._initialized = False
281
-
282
- def process(self, input_data):
283
- """Process the input data."""
284
- if not self._initialized:
285
- self._initialize()
286
- return self._transform(input_data)
287
-
288
- def _initialize(self):
289
- """Initialize internal state."""
290
- self._initialized = True
291
-
292
- def _transform(self, data):
293
- """Transform the data."""
294
- return data`;
295
- break;
296
-
297
- case 'function':
298
- mockCode = `def ${nodeData.name}(${nodeData.params ? nodeData.params.join(', ') : ''}):
299
- """
300
- ${nodeData.docstring || 'A sample function implementation.'}
301
-
302
- Args:
303
- ${nodeData.params ? nodeData.params.map(p => `${p}: Description of ${p}`).join('\n ') : 'None'}
304
-
305
- Returns:
306
- ${nodeData.returns || 'None'}: Return value description
307
- """
308
- # Implementation here
309
- result = None
310
-
311
- # Process logic
312
- for item in range(10):
313
- result = process_item(item)
314
-
315
- return result`;
316
- break;
317
-
318
- case 'method':
319
- mockCode = ` def ${nodeData.name}(self${nodeData.params ? ', ' + nodeData.params.join(', ') : ''}):
320
- """
321
- ${nodeData.docstring || 'A sample method implementation.'}
322
- """
323
- # Method implementation
324
- self._validate()
325
- result = self._process()
326
- return result`;
327
- break;
328
-
329
- default:
330
- mockCode = `# ${nodeData.name}
331
- # Type: ${nodeData.type}
332
- # Path: ${nodeData.path || 'Unknown'}
333
- # Line: ${nodeData.line || 'Unknown'}
334
-
335
- # Code content would appear here
336
- # This is a placeholder for demonstration purposes`;
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;
337
829
  }
830
+
831
+ // Re-render tree
832
+ this.renderTree();
833
+
834
+ // Update selection
835
+ this.selectedNode = d;
338
836
 
339
- this.displayCode(mockCode);
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
+ }
340
841
  }
341
842
 
342
843
  /**
343
- * Display error message
844
+ * Show file details in the left viewer pane
344
845
  */
345
- displayError(message) {
346
- const codeContent = document.getElementById('code-viewer-code-content');
347
- codeContent.textContent = `# Error loading code\n# ${message}`;
348
- codeContent.className = 'language-python';
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;
349
896
  }
350
897
 
351
898
  /**
352
- * Detect language from file path
899
+ * Get operation color
353
900
  */
354
- detectLanguage(path) {
355
- if (!path) return 'python';
356
-
357
- const ext = path.split('.').pop().toLowerCase();
358
- const languageMap = {
359
- 'py': 'python',
360
- 'js': 'javascript',
361
- 'ts': 'typescript',
362
- 'jsx': 'jsx',
363
- 'tsx': 'tsx',
364
- 'css': 'css',
365
- 'html': 'html',
366
- 'json': 'json',
367
- 'yaml': 'yaml',
368
- 'yml': 'yaml',
369
- 'md': 'markdown',
370
- 'sh': 'bash',
371
- 'bash': 'bash',
372
- 'sql': 'sql',
373
- 'go': 'go',
374
- 'rs': 'rust',
375
- 'cpp': 'cpp',
376
- 'c': 'c',
377
- 'h': 'c',
378
- 'hpp': 'cpp',
379
- 'java': 'java',
380
- 'rb': 'ruby',
381
- 'php': 'php'
382
- };
383
-
384
- return languageMap[ext] || 'plaintext';
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
+ }
385
908
  }
386
909
 
387
910
  /**
388
- * Update navigation buttons
911
+ * Escape HTML
389
912
  */
390
- updateNavigation(nodeData) {
391
- // For now, disable navigation buttons
392
- // In a real implementation, these would navigate through the AST
393
- document.getElementById('code-nav-parent').disabled = true;
394
- document.getElementById('code-nav-prev').disabled = true;
395
- document.getElementById('code-nav-next').disabled = true;
396
- document.getElementById('code-nav-position').textContent = '1 / 1';
913
+ escapeHtml(text) {
914
+ const div = document.createElement('div');
915
+ div.textContent = text;
916
+ return div.innerHTML;
397
917
  }
398
918
 
399
919
  /**
400
- * Navigate to parent node
920
+ * Expand all nodes
401
921
  */
402
- navigateToParent() {
403
- console.log('Navigate to parent node');
404
- // Implementation would load parent node's code
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();
405
933
  }
406
934
 
407
935
  /**
408
- * Navigate to previous sibling
936
+ * Collapse all nodes
409
937
  */
410
- navigateToPrevious() {
411
- console.log('Navigate to previous sibling');
412
- // Implementation would load previous sibling's code
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();
413
949
  }
414
950
 
415
951
  /**
416
- * Navigate to next sibling
952
+ * Reset zoom
417
953
  */
418
- navigateToNext() {
419
- console.log('Navigate to next sibling');
420
- // Implementation would load next sibling's code
954
+ resetZoom() {
955
+ if (!this.d3Tree) return;
956
+
957
+ this.d3Tree.svg.transition()
958
+ .duration(750)
959
+ .call(this.d3Tree.zoom.transform, d3.zoomIdentity);
421
960
  }
422
961
 
423
962
  /**
424
- * Copy code to clipboard
963
+ * Update session list in main selector
425
964
  */
426
- async copyCode() {
427
- const codeContent = document.getElementById('code-viewer-code-content');
428
- const code = codeContent.textContent;
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;
429
971
 
430
- try {
431
- await navigator.clipboard.writeText(code);
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
+ }
432
987
 
433
- // Show feedback
434
- const button = document.getElementById('code-copy');
435
- const originalText = button.textContent;
436
- button.textContent = '✅ Copied!';
437
- setTimeout(() => {
438
- button.textContent = originalText;
439
- }, 2000);
440
- } catch (err) {
441
- console.error('Failed to copy code:', err);
442
- alert('Failed to copy code to clipboard');
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;
443
999
  }
444
1000
  }
445
1001
 
446
1002
  /**
447
- * Open file in editor
1003
+ * Update statistics
448
1004
  */
449
- openInEditor() {
450
- if (!this.currentNode || !this.currentNode.path) {
451
- alert('File path not available');
452
- return;
453
- }
454
-
455
- // Emit event to open file
456
- if (this.socket) {
457
- this.socket.emit('file:open', {
458
- path: this.currentNode.path,
459
- line: this.currentNode.line
460
- });
461
- }
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;
462
1012
 
463
- console.log('Opening file in editor:', this.currentNode.path);
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}`;
464
1021
  }
465
1022
  }
466
1023
 
467
- // Create singleton instance
468
- const codeViewer = new CodeViewer();
1024
+ // Create and export singleton instance
1025
+ window.CodeViewer = new CodeViewer();
469
1026
 
470
- // Export for use in other modules
471
- if (typeof window !== 'undefined') {
472
- window.CodeViewer = codeViewer;
473
-
474
- // Initialize when DOM is ready
1027
+ // Auto-initialize when DOM is ready
1028
+ if (document.readyState === 'loading') {
475
1029
  document.addEventListener('DOMContentLoaded', () => {
476
- codeViewer.initialize();
1030
+ window.CodeViewer.initialize();
1031
+
1032
+ // If File 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] File Tree tab is active on load, showing tree...');
1036
+ setTimeout(() => window.CodeViewer.show(), 100);
1037
+ }
477
1038
  });
1039
+ } else {
1040
+ window.CodeViewer.initialize();
1041
+
1042
+ // If File 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] File Tree tab is active, showing tree...');
1046
+ setTimeout(() => window.CodeViewer.show(), 100);
1047
+ }
478
1048
  }
479
1049
 
480
- export default codeViewer;
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 File Tree, forcing show...');
1054
+ setTimeout(() => window.CodeViewer.show(), 50);
1055
+ }
1056
+ });
1057
+
1058
+ // Tab click handling is now done by UIStateManager
1059
+ // CodeViewer.renderContent() is called when the File Tree tab is activated
1060
+
1061
+ // FALLBACK: Periodic check to ensure File Tree tab is properly rendered
1062
+ setInterval(() => {
1063
+ const claudeTreeTab = document.getElementById('claude-tree-tab');
1064
+ const claudeTreeContainer = document.getElementById('claude-tree-container');
1065
+
1066
+ if (claudeTreeTab && claudeTreeTab.classList.contains('active') &&
1067
+ claudeTreeContainer && !claudeTreeContainer.querySelector('.activity-tree-wrapper')) {
1068
+ console.log('[CodeViewer] Periodic check: File Tree tab is active but not properly rendered, fixing...');
1069
+ window.CodeViewer.show();
1070
+ }
1071
+ }, 5000);