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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/BASE_ENGINEER.md +114 -1
- claude_mpm/agents/BASE_OPS.md +156 -1
- claude_mpm/agents/INSTRUCTIONS.md +120 -11
- claude_mpm/agents/WORKFLOW.md +160 -10
- claude_mpm/agents/templates/agentic-coder-optimizer.json +17 -12
- claude_mpm/agents/templates/react_engineer.json +217 -0
- claude_mpm/agents/templates/web_qa.json +40 -4
- claude_mpm/cli/__init__.py +3 -5
- claude_mpm/commands/mpm-browser-monitor.md +370 -0
- claude_mpm/commands/mpm-monitor.md +177 -0
- claude_mpm/dashboard/static/built/components/code-viewer.js +1076 -2
- claude_mpm/dashboard/static/built/components/ui-state-manager.js +465 -2
- claude_mpm/dashboard/static/css/dashboard.css +2 -0
- claude_mpm/dashboard/static/js/browser-console-monitor.js +495 -0
- claude_mpm/dashboard/static/js/components/browser-log-viewer.js +763 -0
- claude_mpm/dashboard/static/js/components/code-viewer.js +931 -340
- claude_mpm/dashboard/static/js/components/diff-viewer.js +891 -0
- claude_mpm/dashboard/static/js/components/file-change-tracker.js +443 -0
- claude_mpm/dashboard/static/js/components/file-change-viewer.js +690 -0
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +307 -19
- claude_mpm/dashboard/static/js/socket-client.js +2 -2
- claude_mpm/dashboard/static/test-browser-monitor.html +470 -0
- claude_mpm/dashboard/templates/index.html +62 -99
- claude_mpm/services/cli/unified_dashboard_manager.py +1 -1
- claude_mpm/services/monitor/daemon.py +69 -36
- claude_mpm/services/monitor/daemon_manager.py +186 -29
- claude_mpm/services/monitor/handlers/browser.py +451 -0
- claude_mpm/services/monitor/server.py +272 -5
- {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/METADATA +1 -1
- {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/RECORD +35 -29
- claude_mpm/agents/templates/agentic-coder-optimizer.md +0 -44
- claude_mpm/agents/templates/agentic_coder_optimizer.json +0 -238
- claude_mpm/agents/templates/test-non-mpm.json +0 -20
- claude_mpm/dashboard/static/dist/components/code-viewer.js +0 -2
- {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/WHEEL +0 -0
- {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.2.39.dist-info → claude_mpm-4.2.42.dist-info}/top_level.txt +0 -0
@@ -1,2 +1,465 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
/**
|
2
|
+
* UI State Manager Module
|
3
|
+
*
|
4
|
+
* Manages UI state including tab switching, card selection, keyboard navigation,
|
5
|
+
* and visual feedback across the dashboard interface.
|
6
|
+
*
|
7
|
+
* WHY: Extracted from main dashboard to centralize UI state management and
|
8
|
+
* provide better separation between business logic and UI state. This makes
|
9
|
+
* the UI behavior more predictable and easier to test.
|
10
|
+
*
|
11
|
+
* DESIGN DECISION: Maintains centralized state for current tab, selected cards,
|
12
|
+
* and navigation context while providing a clean API for other modules to
|
13
|
+
* interact with UI state changes.
|
14
|
+
*/
|
15
|
+
class UIStateManager {
|
16
|
+
constructor() {
|
17
|
+
// Current active tab
|
18
|
+
this.currentTab = 'events';
|
19
|
+
|
20
|
+
// Auto-scroll behavior
|
21
|
+
this.autoScroll = true;
|
22
|
+
|
23
|
+
// Selection state - tracks the currently selected card across all tabs
|
24
|
+
this.selectedCard = {
|
25
|
+
tab: null, // which tab the selection is in
|
26
|
+
index: null, // index of selected item in that tab
|
27
|
+
type: null, // 'event', 'agent', 'tool', 'file'
|
28
|
+
data: null // the actual data object
|
29
|
+
};
|
30
|
+
|
31
|
+
// Navigation state for each tab
|
32
|
+
this.tabNavigation = {
|
33
|
+
events: { selectedIndex: -1, items: [] },
|
34
|
+
agents: { selectedIndex: -1, items: [] },
|
35
|
+
tools: { selectedIndex: -1, items: [] },
|
36
|
+
files: { selectedIndex: -1, items: [] }
|
37
|
+
};
|
38
|
+
|
39
|
+
this.setupEventHandlers();
|
40
|
+
console.log('UI state manager initialized');
|
41
|
+
}
|
42
|
+
|
43
|
+
/**
|
44
|
+
* Set up event handlers for UI interactions
|
45
|
+
*/
|
46
|
+
setupEventHandlers() {
|
47
|
+
this.setupTabNavigation();
|
48
|
+
this.setupUnifiedKeyboardNavigation();
|
49
|
+
}
|
50
|
+
|
51
|
+
/**
|
52
|
+
* Set up tab navigation event listeners
|
53
|
+
*/
|
54
|
+
setupTabNavigation() {
|
55
|
+
// Tab buttons
|
56
|
+
document.querySelectorAll('.tab-button').forEach(button => {
|
57
|
+
button.addEventListener('click', () => {
|
58
|
+
const tabName = this.getTabNameFromButton(button);
|
59
|
+
this.switchTab(tabName);
|
60
|
+
});
|
61
|
+
});
|
62
|
+
}
|
63
|
+
|
64
|
+
/**
|
65
|
+
* Set up unified keyboard navigation across all tabs
|
66
|
+
*/
|
67
|
+
setupUnifiedKeyboardNavigation() {
|
68
|
+
document.addEventListener('keydown', (e) => {
|
69
|
+
// Only handle if not in an input field
|
70
|
+
if (document.activeElement &&
|
71
|
+
['INPUT', 'TEXTAREA', 'SELECT'].includes(document.activeElement.tagName)) {
|
72
|
+
return;
|
73
|
+
}
|
74
|
+
|
75
|
+
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
|
76
|
+
e.preventDefault();
|
77
|
+
this.handleUnifiedArrowNavigation(e.key === 'ArrowDown' ? 1 : -1);
|
78
|
+
} else if (e.key === 'Enter') {
|
79
|
+
e.preventDefault();
|
80
|
+
this.handleUnifiedEnterKey();
|
81
|
+
} else if (e.key === 'Escape') {
|
82
|
+
this.clearUnifiedSelection();
|
83
|
+
}
|
84
|
+
});
|
85
|
+
}
|
86
|
+
|
87
|
+
/**
|
88
|
+
* Get tab name from button element
|
89
|
+
* @param {HTMLElement} button - Tab button element
|
90
|
+
* @returns {string} - Tab name
|
91
|
+
*/
|
92
|
+
getTabNameFromButton(button) {
|
93
|
+
// First check for data-tab attribute
|
94
|
+
const dataTab = button.getAttribute('data-tab');
|
95
|
+
if (dataTab) return dataTab;
|
96
|
+
|
97
|
+
// Fallback to text content matching
|
98
|
+
const text = button.textContent.toLowerCase();
|
99
|
+
if (text.includes('events')) return 'events';
|
100
|
+
if (text.includes('activity')) return 'activity';
|
101
|
+
if (text.includes('agents')) return 'agents';
|
102
|
+
if (text.includes('tools')) return 'tools';
|
103
|
+
if (text.includes('files')) return 'files';
|
104
|
+
if (text.includes('claude tree')) return 'claude-tree';
|
105
|
+
if (text.includes('code')) return 'code';
|
106
|
+
if (text.includes('sessions')) return 'sessions';
|
107
|
+
if (text.includes('system')) return 'system';
|
108
|
+
return 'events';
|
109
|
+
}
|
110
|
+
|
111
|
+
/**
|
112
|
+
* Switch to specified tab
|
113
|
+
* @param {string} tabName - Name of tab to switch to
|
114
|
+
*/
|
115
|
+
switchTab(tabName) {
|
116
|
+
console.log(`[DEBUG] switchTab called with tabName: ${tabName}`);
|
117
|
+
const previousTab = this.currentTab;
|
118
|
+
this.currentTab = tabName;
|
119
|
+
|
120
|
+
// Update tab button active states
|
121
|
+
document.querySelectorAll('.tab-button').forEach(btn => {
|
122
|
+
btn.classList.remove('active');
|
123
|
+
if (this.getTabNameFromButton(btn) === tabName) {
|
124
|
+
btn.classList.add('active');
|
125
|
+
}
|
126
|
+
});
|
127
|
+
|
128
|
+
// Show/hide tab content using CSS classes
|
129
|
+
document.querySelectorAll('.tab-content').forEach(content => {
|
130
|
+
content.classList.remove('active');
|
131
|
+
});
|
132
|
+
|
133
|
+
const activeTab = document.getElementById(`${tabName}-tab`);
|
134
|
+
if (activeTab) {
|
135
|
+
activeTab.classList.add('active');
|
136
|
+
|
137
|
+
// Special handling for Claude Tree tab - ensure it never shows events
|
138
|
+
if (tabName === 'claude-tree') {
|
139
|
+
const claudeTreeContainer = document.getElementById('claude-tree-container');
|
140
|
+
if (claudeTreeContainer) {
|
141
|
+
// Check if events list somehow got into this container
|
142
|
+
const eventsList = claudeTreeContainer.querySelector('#events-list');
|
143
|
+
if (eventsList) {
|
144
|
+
console.warn('[UIStateManager] Found events-list in Claude Tree container, removing it!');
|
145
|
+
eventsList.remove();
|
146
|
+
}
|
147
|
+
|
148
|
+
// Check for event items
|
149
|
+
const eventItems = claudeTreeContainer.querySelectorAll('.event-item');
|
150
|
+
if (eventItems.length > 0) {
|
151
|
+
console.warn('[UIStateManager] Found event items in Claude Tree container, clearing!');
|
152
|
+
eventItems.forEach(item => item.remove());
|
153
|
+
}
|
154
|
+
}
|
155
|
+
}
|
156
|
+
}
|
157
|
+
|
158
|
+
// Clear previous selections when switching tabs
|
159
|
+
this.clearUnifiedSelection();
|
160
|
+
|
161
|
+
// Trigger tab change event for other modules
|
162
|
+
document.dispatchEvent(new CustomEvent('tabChanged', {
|
163
|
+
detail: {
|
164
|
+
newTab: tabName,
|
165
|
+
previousTab: previousTab
|
166
|
+
}
|
167
|
+
}));
|
168
|
+
|
169
|
+
// Auto-scroll to bottom after a brief delay to ensure content is rendered
|
170
|
+
setTimeout(() => {
|
171
|
+
if (this.autoScroll) {
|
172
|
+
this.scrollCurrentTabToBottom();
|
173
|
+
}
|
174
|
+
|
175
|
+
// Special handling for Claude Tree tab - trigger the tree render
|
176
|
+
if (tabName === 'claude-tree' && window.CodeViewer) {
|
177
|
+
window.CodeViewer.show();
|
178
|
+
}
|
179
|
+
}, 100);
|
180
|
+
}
|
181
|
+
|
182
|
+
/**
|
183
|
+
* Handle unified arrow navigation across tabs
|
184
|
+
* @param {number} direction - Navigation direction (1 for down, -1 for up)
|
185
|
+
*/
|
186
|
+
handleUnifiedArrowNavigation(direction) {
|
187
|
+
const tabNav = this.tabNavigation[this.currentTab];
|
188
|
+
if (!tabNav) return;
|
189
|
+
|
190
|
+
let newIndex = tabNav.selectedIndex + direction;
|
191
|
+
|
192
|
+
// Handle bounds
|
193
|
+
if (tabNav.items.length === 0) return;
|
194
|
+
|
195
|
+
if (newIndex < 0) {
|
196
|
+
newIndex = tabNav.items.length - 1;
|
197
|
+
} else if (newIndex >= tabNav.items.length) {
|
198
|
+
newIndex = 0;
|
199
|
+
}
|
200
|
+
|
201
|
+
this.selectCardByIndex(this.currentTab, newIndex);
|
202
|
+
}
|
203
|
+
|
204
|
+
/**
|
205
|
+
* Handle unified Enter key across all tabs
|
206
|
+
*/
|
207
|
+
handleUnifiedEnterKey() {
|
208
|
+
const tabNav = this.tabNavigation[this.currentTab];
|
209
|
+
if (!tabNav || tabNav.selectedIndex === -1) return;
|
210
|
+
|
211
|
+
const selectedElement = tabNav.items[tabNav.selectedIndex];
|
212
|
+
if (selectedElement && selectedElement.onclick) {
|
213
|
+
selectedElement.onclick();
|
214
|
+
}
|
215
|
+
}
|
216
|
+
|
217
|
+
/**
|
218
|
+
* Clear all unified selection states
|
219
|
+
*/
|
220
|
+
clearUnifiedSelection() {
|
221
|
+
// Clear all tab navigation states
|
222
|
+
Object.keys(this.tabNavigation).forEach(tabName => {
|
223
|
+
this.tabNavigation[tabName].selectedIndex = -1;
|
224
|
+
});
|
225
|
+
|
226
|
+
// Clear card selection
|
227
|
+
this.clearCardSelection();
|
228
|
+
}
|
229
|
+
|
230
|
+
/**
|
231
|
+
* Update tab navigation items for current tab
|
232
|
+
* Should be called after tab content is rendered
|
233
|
+
*/
|
234
|
+
updateTabNavigationItems() {
|
235
|
+
const tabNav = this.tabNavigation[this.currentTab];
|
236
|
+
if (!tabNav) return;
|
237
|
+
|
238
|
+
let containerSelector;
|
239
|
+
switch (this.currentTab) {
|
240
|
+
case 'events':
|
241
|
+
containerSelector = '#events-list .event-item';
|
242
|
+
break;
|
243
|
+
case 'agents':
|
244
|
+
containerSelector = '#agents-list .event-item';
|
245
|
+
break;
|
246
|
+
case 'tools':
|
247
|
+
containerSelector = '#tools-list .event-item';
|
248
|
+
break;
|
249
|
+
case 'files':
|
250
|
+
containerSelector = '#files-list .event-item';
|
251
|
+
break;
|
252
|
+
}
|
253
|
+
|
254
|
+
if (containerSelector) {
|
255
|
+
tabNav.items = Array.from(document.querySelectorAll(containerSelector));
|
256
|
+
}
|
257
|
+
}
|
258
|
+
|
259
|
+
/**
|
260
|
+
* Select card by index for specified tab
|
261
|
+
* @param {string} tabName - Tab name
|
262
|
+
* @param {number} index - Index of item to select
|
263
|
+
*/
|
264
|
+
selectCardByIndex(tabName, index) {
|
265
|
+
const tabNav = this.tabNavigation[tabName];
|
266
|
+
if (!tabNav || index < 0 || index >= tabNav.items.length) return;
|
267
|
+
|
268
|
+
// Update navigation state
|
269
|
+
tabNav.selectedIndex = index;
|
270
|
+
|
271
|
+
// Update visual selection
|
272
|
+
this.updateUnifiedSelectionUI();
|
273
|
+
|
274
|
+
// If this is a different tab selection, record the card selection
|
275
|
+
const selectedElement = tabNav.items[index];
|
276
|
+
if (selectedElement) {
|
277
|
+
// Extract data from the element to populate selectedCard
|
278
|
+
this.selectCard(tabName, index, this.getCardType(tabName), index);
|
279
|
+
}
|
280
|
+
|
281
|
+
// Show details for the selected item
|
282
|
+
this.showCardDetails(tabName, index);
|
283
|
+
}
|
284
|
+
|
285
|
+
/**
|
286
|
+
* Update visual selection UI for unified navigation
|
287
|
+
*/
|
288
|
+
updateUnifiedSelectionUI() {
|
289
|
+
// Clear all existing selections
|
290
|
+
document.querySelectorAll('.event-item.keyboard-selected').forEach(el => {
|
291
|
+
el.classList.remove('keyboard-selected');
|
292
|
+
});
|
293
|
+
|
294
|
+
// Apply selection to current tab's selected item
|
295
|
+
const tabNav = this.tabNavigation[this.currentTab];
|
296
|
+
if (tabNav && tabNav.selectedIndex !== -1 && tabNav.items[tabNav.selectedIndex]) {
|
297
|
+
tabNav.items[tabNav.selectedIndex].classList.add('keyboard-selected');
|
298
|
+
}
|
299
|
+
}
|
300
|
+
|
301
|
+
/**
|
302
|
+
* Show card details for specified tab and index
|
303
|
+
* @param {string} tabName - Tab name
|
304
|
+
* @param {number} index - Item index
|
305
|
+
*/
|
306
|
+
showCardDetails(tabName, index) {
|
307
|
+
// Dispatch event for other modules to handle
|
308
|
+
document.dispatchEvent(new CustomEvent('showCardDetails', {
|
309
|
+
detail: {
|
310
|
+
tabName: tabName,
|
311
|
+
index: index
|
312
|
+
}
|
313
|
+
}));
|
314
|
+
}
|
315
|
+
|
316
|
+
/**
|
317
|
+
* Select a specific card
|
318
|
+
* @param {string} tabName - Tab name
|
319
|
+
* @param {number} index - Item index
|
320
|
+
* @param {string} type - Item type
|
321
|
+
* @param {*} data - Item data
|
322
|
+
*/
|
323
|
+
selectCard(tabName, index, type, data) {
|
324
|
+
// Clear previous selection
|
325
|
+
this.clearCardSelection();
|
326
|
+
|
327
|
+
// Update selection state
|
328
|
+
this.selectedCard = {
|
329
|
+
tab: tabName,
|
330
|
+
index: index,
|
331
|
+
type: type,
|
332
|
+
data: data
|
333
|
+
};
|
334
|
+
|
335
|
+
this.updateCardSelectionUI();
|
336
|
+
|
337
|
+
console.log('Card selected:', this.selectedCard);
|
338
|
+
}
|
339
|
+
|
340
|
+
/**
|
341
|
+
* Clear card selection
|
342
|
+
*/
|
343
|
+
clearCardSelection() {
|
344
|
+
// Clear visual selection from all tabs
|
345
|
+
document.querySelectorAll('.event-item.selected, .file-item.selected').forEach(el => {
|
346
|
+
el.classList.remove('selected');
|
347
|
+
});
|
348
|
+
|
349
|
+
// Reset selection state
|
350
|
+
this.selectedCard = {
|
351
|
+
tab: null,
|
352
|
+
index: null,
|
353
|
+
type: null,
|
354
|
+
data: null
|
355
|
+
};
|
356
|
+
}
|
357
|
+
|
358
|
+
/**
|
359
|
+
* Update card selection UI
|
360
|
+
*/
|
361
|
+
updateCardSelectionUI() {
|
362
|
+
if (!this.selectedCard.tab || this.selectedCard.index === null) return;
|
363
|
+
|
364
|
+
// Get the list container for the selected tab
|
365
|
+
let listContainer;
|
366
|
+
switch (this.selectedCard.tab) {
|
367
|
+
case 'events':
|
368
|
+
listContainer = document.getElementById('events-list');
|
369
|
+
break;
|
370
|
+
case 'agents':
|
371
|
+
listContainer = document.getElementById('agents-list');
|
372
|
+
break;
|
373
|
+
case 'tools':
|
374
|
+
listContainer = document.getElementById('tools-list');
|
375
|
+
break;
|
376
|
+
case 'files':
|
377
|
+
listContainer = document.getElementById('files-list');
|
378
|
+
break;
|
379
|
+
}
|
380
|
+
|
381
|
+
if (listContainer) {
|
382
|
+
const items = listContainer.querySelectorAll('.event-item, .file-item');
|
383
|
+
if (items[this.selectedCard.index]) {
|
384
|
+
items[this.selectedCard.index].classList.add('selected');
|
385
|
+
}
|
386
|
+
}
|
387
|
+
}
|
388
|
+
|
389
|
+
/**
|
390
|
+
* Get card type based on tab name
|
391
|
+
* @param {string} tabName - Tab name
|
392
|
+
* @returns {string} - Card type
|
393
|
+
*/
|
394
|
+
getCardType(tabName) {
|
395
|
+
switch (tabName) {
|
396
|
+
case 'events': return 'event';
|
397
|
+
case 'agents': return 'agent';
|
398
|
+
case 'tools': return 'tool';
|
399
|
+
case 'files': return 'file';
|
400
|
+
default: return 'unknown';
|
401
|
+
}
|
402
|
+
}
|
403
|
+
|
404
|
+
/**
|
405
|
+
* Scroll current tab to bottom
|
406
|
+
*/
|
407
|
+
scrollCurrentTabToBottom() {
|
408
|
+
const tabId = `${this.currentTab}-list`;
|
409
|
+
const element = document.getElementById(tabId);
|
410
|
+
if (element && this.autoScroll) {
|
411
|
+
element.scrollTop = element.scrollHeight;
|
412
|
+
}
|
413
|
+
}
|
414
|
+
|
415
|
+
/**
|
416
|
+
* Clear selection for cleanup
|
417
|
+
*/
|
418
|
+
clearSelection() {
|
419
|
+
this.clearCardSelection();
|
420
|
+
this.clearUnifiedSelection();
|
421
|
+
}
|
422
|
+
|
423
|
+
/**
|
424
|
+
* Get current tab name
|
425
|
+
* @returns {string} - Current tab name
|
426
|
+
*/
|
427
|
+
getCurrentTab() {
|
428
|
+
return this.currentTab;
|
429
|
+
}
|
430
|
+
|
431
|
+
/**
|
432
|
+
* Get selected card info
|
433
|
+
* @returns {Object} - Selected card state
|
434
|
+
*/
|
435
|
+
getSelectedCard() {
|
436
|
+
return { ...this.selectedCard };
|
437
|
+
}
|
438
|
+
|
439
|
+
/**
|
440
|
+
* Get tab navigation state
|
441
|
+
* @returns {Object} - Tab navigation state
|
442
|
+
*/
|
443
|
+
getTabNavigation() {
|
444
|
+
return { ...this.tabNavigation };
|
445
|
+
}
|
446
|
+
|
447
|
+
/**
|
448
|
+
* Set auto-scroll behavior
|
449
|
+
* @param {boolean} enabled - Whether to enable auto-scroll
|
450
|
+
*/
|
451
|
+
setAutoScroll(enabled) {
|
452
|
+
this.autoScroll = enabled;
|
453
|
+
}
|
454
|
+
|
455
|
+
/**
|
456
|
+
* Get auto-scroll state
|
457
|
+
* @returns {boolean} - Auto-scroll enabled state
|
458
|
+
*/
|
459
|
+
getAutoScroll() {
|
460
|
+
return this.autoScroll;
|
461
|
+
}
|
462
|
+
}
|
463
|
+
// ES6 Module export
|
464
|
+
export { UIStateManager };
|
465
|
+
export default UIStateManager;
|
@@ -867,6 +867,7 @@ button:active {
|
|
867
867
|
}
|
868
868
|
|
869
869
|
.tab-button {
|
870
|
+
display: inline-block;
|
870
871
|
padding: 10px 10px;
|
871
872
|
background: none;
|
872
873
|
border: none;
|
@@ -875,6 +876,7 @@ button:active {
|
|
875
876
|
font-size: 14px;
|
876
877
|
font-weight: 500;
|
877
878
|
color: #64748b;
|
879
|
+
text-decoration: none;
|
878
880
|
transition: all 0.2s;
|
879
881
|
}
|
880
882
|
|