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,2 +1,1076 @@
1
- const e=new class{constructor(){this.modal=null,this.currentNode=null,this.socket=null,this.initialized=!1,this.codeCache=new Map}initialize(){this.initialized||(this.createModal(),this.setupEventHandlers(),this.subscribeToEvents(),this.initialized=!0,console.log("Code viewer initialized"))}createModal(){document.body.insertAdjacentHTML("beforeend",'\n <div class="code-viewer-modal" id="code-viewer-modal">\n <div class="code-viewer-content">\n <div class="code-viewer-header">\n <div class="code-viewer-title" id="code-viewer-title">\n Loading...\n </div>\n <div class="code-viewer-info">\n <span id="code-viewer-type">Type: --</span>\n <span id="code-viewer-lines">Lines: --</span>\n <span id="code-viewer-complexity">Complexity: --</span>\n </div>\n <button class="code-viewer-close" id="code-viewer-close">×</button>\n </div>\n <div class="code-viewer-body">\n <pre class="code-viewer-code line-numbers" id="code-viewer-code">\n <code class="language-python" id="code-viewer-code-content"></code>\n </pre>\n </div>\n <div class="code-viewer-navigation">\n <div class="nav-group">\n <button class="code-nav-button" id="code-nav-parent" disabled>\n ⬆️ Parent\n </button>\n <button class="code-nav-button" id="code-nav-prev" disabled>\n ⬅️ Previous\n </button>\n <button class="code-nav-button" id="code-nav-next" disabled>\n ➡️ Next\n </button>\n </div>\n <div class="nav-info">\n <span id="code-nav-position">-- / --</span>\n </div>\n <div class="nav-actions">\n <button class="code-nav-button" id="code-copy">\n 📋 Copy\n </button>\n <button class="code-nav-button" id="code-open-file">\n 📂 Open File\n </button>\n </div>\n </div>\n </div>\n </div>\n '),this.modal=document.getElementById("code-viewer-modal")}setupEventHandlers(){document.getElementById("code-viewer-close").addEventListener("click",()=>{this.hide()}),this.modal.addEventListener("click",e=>{e.target===this.modal&&this.hide()}),document.addEventListener("keydown",e=>{"Escape"===e.key&&this.modal.classList.contains("show")&&this.hide()}),document.getElementById("code-nav-parent").addEventListener("click",()=>{this.navigateToParent()}),document.getElementById("code-nav-prev").addEventListener("click",()=>{this.navigateToPrevious()}),document.getElementById("code-nav-next").addEventListener("click",()=>{this.navigateToNext()}),document.getElementById("code-copy").addEventListener("click",()=>{this.copyCode()}),document.getElementById("code-open-file").addEventListener("click",()=>{this.openInEditor()})}subscribeToEvents(){window.socket&&(this.socket=window.socket,this.socket.on("code:content:response",e=>{this.handleCodeContent(e)}))}show(e){this.initialized||this.initialize(),this.currentNode=e,this.modal.classList.add("show"),this.updateHeader(e),this.loadCode(e),this.updateNavigation(e)}hide(){this.modal.classList.remove("show"),this.currentNode=null}updateHeader(e){document.getElementById("code-viewer-title").textContent=`${e.name} (${e.path||"Unknown"})`,document.getElementById("code-viewer-type").textContent=`Type: ${e.type}`,document.getElementById("code-viewer-lines").textContent=`Lines: ${e.lines||"--"}`,document.getElementById("code-viewer-complexity").textContent=`Complexity: ${e.complexity||"--"}`}loadCode(e){const t=document.getElementById("code-viewer-code-content"),n=`${e.path}:${e.line}`;this.codeCache.has(n)?this.displayCode(this.codeCache.get(n)):(t.textContent="Loading code...",this.socket?this.socket.emit("code:content:request",{path:e.path,line:e.line,type:e.type,name:e.name}):this.displayMockCode(e))}handleCodeContent(e){if(!e.success)return void this.displayError(e.error||"Failed to load code");const t=`${e.path}:${e.line}`;this.codeCache.set(t,e.content),this.displayCode(e.content)}displayCode(e){const t=document.getElementById("code-viewer-code-content"),n=document.getElementById("code-viewer-code");t.textContent=e;const i=this.detectLanguage(this.currentNode.path);t.className=`language-${i}`,window.Prism&&(Prism.highlightElement(t),Prism.plugins&&Prism.plugins.lineNumbers&&Prism.plugins.lineNumbers.resize(n))}displayMockCode(e){let t="";switch(e.type){case"class":t=`class ${e.name}:\n """\n ${e.docstring||"A sample class implementation."}\n """\n \n def __init__(self):\n """Initialize the ${e.name} class."""\n self._data = {}\n self._initialized = False\n \n def process(self, input_data):\n """Process the input data."""\n if not self._initialized:\n self._initialize()\n return self._transform(input_data)\n \n def _initialize(self):\n """Initialize internal state."""\n self._initialized = True\n \n def _transform(self, data):\n """Transform the data."""\n return data`;break;case"function":t=`def ${e.name}(${e.params?e.params.join(", "):""}):\n """\n ${e.docstring||"A sample function implementation."}\n \n Args:\n ${e.params?e.params.map(e=>`${e}: Description of ${e}`).join("\n "):"None"}\n \n Returns:\n ${e.returns||"None"}: Return value description\n """\n # Implementation here\n result = None\n \n # Process logic\n for item in range(10):\n result = process_item(item)\n \n return result`;break;case"method":t=` def ${e.name}(self${e.params?", "+e.params.join(", "):""}):\n """\n ${e.docstring||"A sample method implementation."}\n """\n # Method implementation\n self._validate()\n result = self._process()\n return result`;break;default:t=`# ${e.name}\n# Type: ${e.type}\n# Path: ${e.path||"Unknown"}\n# Line: ${e.line||"Unknown"}\n\n# Code content would appear here\n# This is a placeholder for demonstration purposes`}this.displayCode(t)}displayError(e){const t=document.getElementById("code-viewer-code-content");t.textContent=`# Error loading code\n# ${e}`,t.className="language-python"}detectLanguage(e){if(!e)return"python";return{py:"python",js:"javascript",ts:"typescript",jsx:"jsx",tsx:"tsx",css:"css",html:"html",json:"json",yaml:"yaml",yml:"yaml",md:"markdown",sh:"bash",bash:"bash",sql:"sql",go:"go",rs:"rust",cpp:"cpp",c:"c",h:"c",hpp:"cpp",java:"java",rb:"ruby",php:"php"}[e.split(".").pop().toLowerCase()]||"plaintext"}updateNavigation(e){document.getElementById("code-nav-parent").disabled=!0,document.getElementById("code-nav-prev").disabled=!0,document.getElementById("code-nav-next").disabled=!0,document.getElementById("code-nav-position").textContent="1 / 1"}navigateToParent(){console.log("Navigate to parent node")}navigateToPrevious(){console.log("Navigate to previous sibling")}navigateToNext(){console.log("Navigate to next sibling")}async copyCode(){const e=document.getElementById("code-viewer-code-content").textContent;try{await navigator.clipboard.writeText(e);const t=document.getElementById("code-copy"),n=t.textContent;t.textContent="✅ Copied!",setTimeout(()=>{t.textContent=n},2e3)}catch(t){console.error("Failed to copy code:",t),alert("Failed to copy code to clipboard")}}openInEditor(){this.currentNode&&this.currentNode.path?(this.socket&&this.socket.emit("file:open",{path:this.currentNode.path,line:this.currentNode.line}),console.log("Opening file in editor:",this.currentNode.path)):alert("File path not available")}};"undefined"!=typeof window&&(window.CodeViewer=e,document.addEventListener("DOMContentLoaded",()=>{e.initialize()}));
2
- //# sourceMappingURL=code-viewer.js.map
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);