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
@@ -0,0 +1,690 @@
|
|
1
|
+
/**
|
2
|
+
* File Change Viewer Component
|
3
|
+
*
|
4
|
+
* Displays files edited by Claude in a tree structure grouped by working directory.
|
5
|
+
* Integrates with FileChangeTracker for tracking changes and DiffViewer for showing diffs.
|
6
|
+
* Supports session-based filtering and displays change indicators.
|
7
|
+
*/
|
8
|
+
|
9
|
+
class FileChangeViewer {
|
10
|
+
constructor() {
|
11
|
+
this.modal = null;
|
12
|
+
this.currentFile = null;
|
13
|
+
this.initialized = false;
|
14
|
+
this.fileTracker = null;
|
15
|
+
this.diffViewer = null;
|
16
|
+
this.currentSessionId = null;
|
17
|
+
this.treeContainer = null;
|
18
|
+
}
|
19
|
+
|
20
|
+
/**
|
21
|
+
* Initialize the file change viewer
|
22
|
+
*/
|
23
|
+
initialize() {
|
24
|
+
if (this.initialized) return;
|
25
|
+
|
26
|
+
// Create dependent components
|
27
|
+
this.fileTracker = new FileChangeTracker();
|
28
|
+
this.diffViewer = new DiffViewer();
|
29
|
+
|
30
|
+
this.createModal();
|
31
|
+
this.setupEventHandlers();
|
32
|
+
this.subscribeToEvents();
|
33
|
+
this.injectStyles();
|
34
|
+
|
35
|
+
this.initialized = true;
|
36
|
+
console.log('File change viewer initialized');
|
37
|
+
}
|
38
|
+
|
39
|
+
/**
|
40
|
+
* Create modal DOM structure
|
41
|
+
*/
|
42
|
+
createModal() {
|
43
|
+
const modalHtml = `
|
44
|
+
<div class="file-change-modal" id="file-change-modal">
|
45
|
+
<div class="file-change-content">
|
46
|
+
<div class="file-change-header">
|
47
|
+
<div class="file-change-title">
|
48
|
+
<span class="title-icon">📝</span>
|
49
|
+
<span class="title-text">Files Changed by Claude</span>
|
50
|
+
</div>
|
51
|
+
<div class="file-change-controls">
|
52
|
+
<div class="session-filter">
|
53
|
+
<label>Session:</label>
|
54
|
+
<select id="file-session-filter" class="session-select">
|
55
|
+
<option value="">All Sessions</option>
|
56
|
+
</select>
|
57
|
+
</div>
|
58
|
+
<div class="file-stats" id="file-stats">
|
59
|
+
<span class="stat-item">
|
60
|
+
<span class="stat-icon">📄</span>
|
61
|
+
<span class="stat-value" id="total-files">0</span>
|
62
|
+
<span class="stat-label">files</span>
|
63
|
+
</span>
|
64
|
+
<span class="stat-item">
|
65
|
+
<span class="stat-icon">✏️</span>
|
66
|
+
<span class="stat-value" id="total-edits">0</span>
|
67
|
+
<span class="stat-label">edits</span>
|
68
|
+
</span>
|
69
|
+
<span class="stat-item">
|
70
|
+
<span class="stat-icon">💾</span>
|
71
|
+
<span class="stat-value" id="total-writes">0</span>
|
72
|
+
<span class="stat-label">writes</span>
|
73
|
+
</span>
|
74
|
+
</div>
|
75
|
+
<button class="file-change-close" id="file-change-close">×</button>
|
76
|
+
</div>
|
77
|
+
</div>
|
78
|
+
<div class="file-change-body">
|
79
|
+
<div class="file-tree-container" id="file-tree-container">
|
80
|
+
<!-- File tree will be inserted here -->
|
81
|
+
</div>
|
82
|
+
</div>
|
83
|
+
</div>
|
84
|
+
</div>
|
85
|
+
`;
|
86
|
+
|
87
|
+
// Add modal to body
|
88
|
+
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
89
|
+
this.modal = document.getElementById('file-change-modal');
|
90
|
+
this.treeContainer = document.getElementById('file-tree-container');
|
91
|
+
}
|
92
|
+
|
93
|
+
/**
|
94
|
+
* Inject CSS styles
|
95
|
+
*/
|
96
|
+
injectStyles() {
|
97
|
+
const styleId = 'file-change-viewer-styles';
|
98
|
+
if (document.getElementById(styleId)) return;
|
99
|
+
|
100
|
+
const styles = `
|
101
|
+
<style id="${styleId}">
|
102
|
+
.file-change-modal {
|
103
|
+
display: none;
|
104
|
+
position: fixed;
|
105
|
+
top: 0;
|
106
|
+
left: 0;
|
107
|
+
right: 0;
|
108
|
+
bottom: 0;
|
109
|
+
background: rgba(0, 0, 0, 0.5);
|
110
|
+
z-index: 9999;
|
111
|
+
padding: 20px;
|
112
|
+
overflow: auto;
|
113
|
+
}
|
114
|
+
|
115
|
+
.file-change-modal.show {
|
116
|
+
display: flex;
|
117
|
+
align-items: center;
|
118
|
+
justify-content: center;
|
119
|
+
}
|
120
|
+
|
121
|
+
.file-change-content {
|
122
|
+
background: white;
|
123
|
+
border-radius: 8px;
|
124
|
+
width: 90%;
|
125
|
+
max-width: 1200px;
|
126
|
+
height: 80%;
|
127
|
+
display: flex;
|
128
|
+
flex-direction: column;
|
129
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
130
|
+
}
|
131
|
+
|
132
|
+
.file-change-header {
|
133
|
+
display: flex;
|
134
|
+
justify-content: space-between;
|
135
|
+
align-items: center;
|
136
|
+
padding: 16px 20px;
|
137
|
+
border-bottom: 1px solid #e2e8f0;
|
138
|
+
background: #f8fafc;
|
139
|
+
border-radius: 8px 8px 0 0;
|
140
|
+
}
|
141
|
+
|
142
|
+
.file-change-title {
|
143
|
+
display: flex;
|
144
|
+
align-items: center;
|
145
|
+
gap: 8px;
|
146
|
+
font-size: 16px;
|
147
|
+
font-weight: 600;
|
148
|
+
color: #2d3748;
|
149
|
+
}
|
150
|
+
|
151
|
+
.title-icon {
|
152
|
+
font-size: 20px;
|
153
|
+
}
|
154
|
+
|
155
|
+
.file-change-controls {
|
156
|
+
display: flex;
|
157
|
+
align-items: center;
|
158
|
+
gap: 20px;
|
159
|
+
}
|
160
|
+
|
161
|
+
.session-filter {
|
162
|
+
display: flex;
|
163
|
+
align-items: center;
|
164
|
+
gap: 8px;
|
165
|
+
}
|
166
|
+
|
167
|
+
.session-filter label {
|
168
|
+
font-size: 13px;
|
169
|
+
color: #4a5568;
|
170
|
+
}
|
171
|
+
|
172
|
+
.session-select {
|
173
|
+
padding: 4px 8px;
|
174
|
+
border: 1px solid #cbd5e0;
|
175
|
+
border-radius: 4px;
|
176
|
+
font-size: 13px;
|
177
|
+
background: white;
|
178
|
+
}
|
179
|
+
|
180
|
+
.file-stats {
|
181
|
+
display: flex;
|
182
|
+
gap: 16px;
|
183
|
+
}
|
184
|
+
|
185
|
+
.stat-item {
|
186
|
+
display: flex;
|
187
|
+
align-items: center;
|
188
|
+
gap: 4px;
|
189
|
+
font-size: 13px;
|
190
|
+
color: #4a5568;
|
191
|
+
}
|
192
|
+
|
193
|
+
.stat-icon {
|
194
|
+
font-size: 14px;
|
195
|
+
}
|
196
|
+
|
197
|
+
.stat-value {
|
198
|
+
font-weight: 600;
|
199
|
+
color: #2d3748;
|
200
|
+
}
|
201
|
+
|
202
|
+
.file-change-close {
|
203
|
+
width: 32px;
|
204
|
+
height: 32px;
|
205
|
+
border: none;
|
206
|
+
background: transparent;
|
207
|
+
font-size: 24px;
|
208
|
+
cursor: pointer;
|
209
|
+
color: #718096;
|
210
|
+
display: flex;
|
211
|
+
align-items: center;
|
212
|
+
justify-content: center;
|
213
|
+
border-radius: 4px;
|
214
|
+
transition: all 0.2s;
|
215
|
+
}
|
216
|
+
|
217
|
+
.file-change-close:hover {
|
218
|
+
background: #e2e8f0;
|
219
|
+
color: #2d3748;
|
220
|
+
}
|
221
|
+
|
222
|
+
.file-change-body {
|
223
|
+
flex: 1;
|
224
|
+
overflow: auto;
|
225
|
+
padding: 20px;
|
226
|
+
background: #fafbfc;
|
227
|
+
}
|
228
|
+
|
229
|
+
.file-tree-container {
|
230
|
+
display: flex;
|
231
|
+
flex-direction: column;
|
232
|
+
gap: 16px;
|
233
|
+
}
|
234
|
+
|
235
|
+
/* Directory groups */
|
236
|
+
.directory-group {
|
237
|
+
background: white;
|
238
|
+
border: 1px solid #e2e8f0;
|
239
|
+
border-radius: 6px;
|
240
|
+
overflow: hidden;
|
241
|
+
}
|
242
|
+
|
243
|
+
.directory-header {
|
244
|
+
display: flex;
|
245
|
+
align-items: center;
|
246
|
+
justify-content: space-between;
|
247
|
+
padding: 12px 16px;
|
248
|
+
background: #f8fafc;
|
249
|
+
border-bottom: 1px solid #e2e8f0;
|
250
|
+
cursor: pointer;
|
251
|
+
transition: background 0.2s;
|
252
|
+
}
|
253
|
+
|
254
|
+
.directory-header:hover {
|
255
|
+
background: #f1f5f9;
|
256
|
+
}
|
257
|
+
|
258
|
+
.directory-info {
|
259
|
+
display: flex;
|
260
|
+
align-items: center;
|
261
|
+
gap: 8px;
|
262
|
+
}
|
263
|
+
|
264
|
+
.directory-icon {
|
265
|
+
font-size: 16px;
|
266
|
+
color: #4299e1;
|
267
|
+
}
|
268
|
+
|
269
|
+
.directory-path {
|
270
|
+
font-family: 'SF Mono', Monaco, monospace;
|
271
|
+
font-size: 13px;
|
272
|
+
font-weight: 600;
|
273
|
+
color: #2d3748;
|
274
|
+
}
|
275
|
+
|
276
|
+
.directory-stats {
|
277
|
+
display: flex;
|
278
|
+
gap: 12px;
|
279
|
+
font-size: 12px;
|
280
|
+
color: #718096;
|
281
|
+
}
|
282
|
+
|
283
|
+
.directory-files {
|
284
|
+
padding: 12px;
|
285
|
+
display: none;
|
286
|
+
}
|
287
|
+
|
288
|
+
.directory-group.expanded .directory-files {
|
289
|
+
display: block;
|
290
|
+
}
|
291
|
+
|
292
|
+
.directory-group.expanded .directory-icon::before {
|
293
|
+
content: '📂';
|
294
|
+
}
|
295
|
+
|
296
|
+
.directory-group:not(.expanded) .directory-icon::before {
|
297
|
+
content: '📁';
|
298
|
+
}
|
299
|
+
|
300
|
+
/* File items */
|
301
|
+
.file-item {
|
302
|
+
display: flex;
|
303
|
+
align-items: center;
|
304
|
+
justify-content: space-between;
|
305
|
+
padding: 10px 12px;
|
306
|
+
background: #f8fafc;
|
307
|
+
border: 1px solid #e2e8f0;
|
308
|
+
border-radius: 4px;
|
309
|
+
margin-bottom: 8px;
|
310
|
+
cursor: pointer;
|
311
|
+
transition: all 0.2s;
|
312
|
+
}
|
313
|
+
|
314
|
+
.file-item:hover {
|
315
|
+
background: white;
|
316
|
+
border-color: #4299e1;
|
317
|
+
transform: translateX(4px);
|
318
|
+
}
|
319
|
+
|
320
|
+
.file-item:last-child {
|
321
|
+
margin-bottom: 0;
|
322
|
+
}
|
323
|
+
|
324
|
+
.file-info {
|
325
|
+
display: flex;
|
326
|
+
align-items: center;
|
327
|
+
gap: 8px;
|
328
|
+
flex: 1;
|
329
|
+
}
|
330
|
+
|
331
|
+
.file-icon {
|
332
|
+
font-size: 14px;
|
333
|
+
}
|
334
|
+
|
335
|
+
.file-name {
|
336
|
+
font-family: 'SF Mono', Monaco, monospace;
|
337
|
+
font-size: 13px;
|
338
|
+
color: #2d3748;
|
339
|
+
font-weight: 500;
|
340
|
+
}
|
341
|
+
|
342
|
+
.file-badges {
|
343
|
+
display: flex;
|
344
|
+
gap: 6px;
|
345
|
+
}
|
346
|
+
|
347
|
+
.file-badge {
|
348
|
+
padding: 2px 6px;
|
349
|
+
font-size: 11px;
|
350
|
+
border-radius: 3px;
|
351
|
+
font-weight: 600;
|
352
|
+
}
|
353
|
+
|
354
|
+
.badge-edit {
|
355
|
+
background: #fef3c7;
|
356
|
+
color: #92400e;
|
357
|
+
}
|
358
|
+
|
359
|
+
.badge-write {
|
360
|
+
background: #dbeafe;
|
361
|
+
color: #1e40af;
|
362
|
+
}
|
363
|
+
|
364
|
+
.badge-read {
|
365
|
+
background: #e0e7ff;
|
366
|
+
color: #3730a3;
|
367
|
+
}
|
368
|
+
|
369
|
+
.file-timestamp {
|
370
|
+
font-size: 11px;
|
371
|
+
color: #a0aec0;
|
372
|
+
}
|
373
|
+
|
374
|
+
/* Empty state */
|
375
|
+
.empty-state {
|
376
|
+
text-align: center;
|
377
|
+
padding: 40px;
|
378
|
+
color: #718096;
|
379
|
+
}
|
380
|
+
|
381
|
+
.empty-state-icon {
|
382
|
+
font-size: 48px;
|
383
|
+
margin-bottom: 16px;
|
384
|
+
}
|
385
|
+
|
386
|
+
.empty-state-text {
|
387
|
+
font-size: 14px;
|
388
|
+
}
|
389
|
+
</style>
|
390
|
+
`;
|
391
|
+
|
392
|
+
document.head.insertAdjacentHTML('beforeend', styles);
|
393
|
+
}
|
394
|
+
|
395
|
+
/**
|
396
|
+
* Setup event handlers
|
397
|
+
*/
|
398
|
+
setupEventHandlers() {
|
399
|
+
// Close button
|
400
|
+
document.getElementById('file-change-close').addEventListener('click', () => {
|
401
|
+
this.hide();
|
402
|
+
});
|
403
|
+
|
404
|
+
// Close on backdrop click
|
405
|
+
this.modal.addEventListener('click', (e) => {
|
406
|
+
if (e.target === this.modal) {
|
407
|
+
this.hide();
|
408
|
+
}
|
409
|
+
});
|
410
|
+
|
411
|
+
// Close on ESC key
|
412
|
+
document.addEventListener('keydown', (e) => {
|
413
|
+
if (e.key === 'Escape' && this.modal.classList.contains('show')) {
|
414
|
+
this.hide();
|
415
|
+
}
|
416
|
+
});
|
417
|
+
|
418
|
+
// Session filter
|
419
|
+
document.getElementById('file-session-filter').addEventListener('change', (e) => {
|
420
|
+
this.currentSessionId = e.target.value;
|
421
|
+
this.updateFileTree();
|
422
|
+
});
|
423
|
+
}
|
424
|
+
|
425
|
+
/**
|
426
|
+
* Subscribe to events
|
427
|
+
*/
|
428
|
+
subscribeToEvents() {
|
429
|
+
// Subscribe to socket if available
|
430
|
+
if (window.socket) {
|
431
|
+
// We might want to listen for file operation events
|
432
|
+
}
|
433
|
+
|
434
|
+
// Listen for event viewer updates
|
435
|
+
if (window.eventViewer) {
|
436
|
+
// Hook into event updates to refresh our view
|
437
|
+
const originalAddEvent = window.eventViewer.addEvent;
|
438
|
+
window.eventViewer.addEvent = (event) => {
|
439
|
+
originalAddEvent.call(window.eventViewer, event);
|
440
|
+
if (this.modal && this.modal.classList.contains('show')) {
|
441
|
+
this.updateFromEvents();
|
442
|
+
}
|
443
|
+
};
|
444
|
+
}
|
445
|
+
}
|
446
|
+
|
447
|
+
/**
|
448
|
+
* Show the file change viewer
|
449
|
+
* @param {Array} events - Optional events to display
|
450
|
+
*/
|
451
|
+
show(events = null) {
|
452
|
+
if (!this.initialized) {
|
453
|
+
this.initialize();
|
454
|
+
}
|
455
|
+
|
456
|
+
this.modal.classList.add('show');
|
457
|
+
|
458
|
+
// Update with events
|
459
|
+
if (events) {
|
460
|
+
this.fileTracker.updateEvents(events);
|
461
|
+
} else if (window.eventViewer) {
|
462
|
+
this.fileTracker.updateEvents(window.eventViewer.events);
|
463
|
+
}
|
464
|
+
|
465
|
+
// Update session filter
|
466
|
+
this.updateSessionFilter();
|
467
|
+
|
468
|
+
// Display file tree
|
469
|
+
this.updateFileTree();
|
470
|
+
|
471
|
+
// Update statistics
|
472
|
+
this.updateStatistics();
|
473
|
+
}
|
474
|
+
|
475
|
+
/**
|
476
|
+
* Hide the file change viewer
|
477
|
+
*/
|
478
|
+
hide() {
|
479
|
+
this.modal.classList.remove('show');
|
480
|
+
this.currentFile = null;
|
481
|
+
}
|
482
|
+
|
483
|
+
/**
|
484
|
+
* Update from events
|
485
|
+
*/
|
486
|
+
updateFromEvents() {
|
487
|
+
if (window.eventViewer) {
|
488
|
+
this.fileTracker.updateEvents(window.eventViewer.events);
|
489
|
+
this.updateFileTree();
|
490
|
+
this.updateStatistics();
|
491
|
+
}
|
492
|
+
}
|
493
|
+
|
494
|
+
/**
|
495
|
+
* Update session filter dropdown
|
496
|
+
*/
|
497
|
+
updateSessionFilter() {
|
498
|
+
const select = document.getElementById('file-session-filter');
|
499
|
+
const sessions = Array.from(this.fileTracker.sessionData.keys());
|
500
|
+
|
501
|
+
// Clear existing options except "All Sessions"
|
502
|
+
select.innerHTML = '<option value="">All Sessions</option>';
|
503
|
+
|
504
|
+
// Add session options
|
505
|
+
sessions.forEach(sessionId => {
|
506
|
+
const option = document.createElement('option');
|
507
|
+
option.value = sessionId;
|
508
|
+
option.textContent = `Session: ${sessionId.substring(0, 8)}...`;
|
509
|
+
select.appendChild(option);
|
510
|
+
});
|
511
|
+
|
512
|
+
// Restore selection
|
513
|
+
if (this.currentSessionId) {
|
514
|
+
select.value = this.currentSessionId;
|
515
|
+
}
|
516
|
+
}
|
517
|
+
|
518
|
+
/**
|
519
|
+
* Update file tree display
|
520
|
+
*/
|
521
|
+
updateFileTree() {
|
522
|
+
const tree = this.fileTracker.getFileTree(this.currentSessionId);
|
523
|
+
|
524
|
+
if (Object.keys(tree).length === 0) {
|
525
|
+
this.treeContainer.innerHTML = `
|
526
|
+
<div class="empty-state">
|
527
|
+
<div class="empty-state-icon">📁</div>
|
528
|
+
<div class="empty-state-text">No file changes detected</div>
|
529
|
+
</div>
|
530
|
+
`;
|
531
|
+
return;
|
532
|
+
}
|
533
|
+
|
534
|
+
// Build tree HTML
|
535
|
+
const treeHtml = Object.entries(tree)
|
536
|
+
.sort((a, b) => b[1].totalOperations - a[1].totalOperations)
|
537
|
+
.map(([dirPath, dirData]) => this.renderDirectoryGroup(dirPath, dirData))
|
538
|
+
.join('');
|
539
|
+
|
540
|
+
this.treeContainer.innerHTML = treeHtml;
|
541
|
+
|
542
|
+
// Add event handlers
|
543
|
+
this.attachTreeHandlers();
|
544
|
+
}
|
545
|
+
|
546
|
+
/**
|
547
|
+
* Render a directory group
|
548
|
+
* @param {string} dirPath - Directory path
|
549
|
+
* @param {Object} dirData - Directory data
|
550
|
+
* @returns {string} HTML
|
551
|
+
*/
|
552
|
+
renderDirectoryGroup(dirPath, dirData) {
|
553
|
+
const displayPath = dirPath === 'unknown' ? 'Unknown Directory' : dirPath;
|
554
|
+
|
555
|
+
return `
|
556
|
+
<div class="directory-group" data-path="${dirPath}">
|
557
|
+
<div class="directory-header">
|
558
|
+
<div class="directory-info">
|
559
|
+
<span class="directory-icon"></span>
|
560
|
+
<span class="directory-path">${this.escapeHtml(displayPath)}</span>
|
561
|
+
</div>
|
562
|
+
<div class="directory-stats">
|
563
|
+
<span>${dirData.files.length} files</span>
|
564
|
+
<span>${dirData.totalEdits} edits</span>
|
565
|
+
<span>${dirData.totalWrites} writes</span>
|
566
|
+
</div>
|
567
|
+
</div>
|
568
|
+
<div class="directory-files">
|
569
|
+
${dirData.files.map(file => this.renderFileItem(file)).join('')}
|
570
|
+
</div>
|
571
|
+
</div>
|
572
|
+
`;
|
573
|
+
}
|
574
|
+
|
575
|
+
/**
|
576
|
+
* Render a file item
|
577
|
+
* @param {Object} fileData - File data
|
578
|
+
* @returns {string} HTML
|
579
|
+
*/
|
580
|
+
renderFileItem(fileData) {
|
581
|
+
const badges = [];
|
582
|
+
if (fileData.totalEdits > 0) {
|
583
|
+
badges.push(`<span class="file-badge badge-edit">✏️ ${fileData.totalEdits}</span>`);
|
584
|
+
}
|
585
|
+
if (fileData.totalWrites > 0) {
|
586
|
+
badges.push(`<span class="file-badge badge-write">💾 ${fileData.totalWrites}</span>`);
|
587
|
+
}
|
588
|
+
if (fileData.totalReads > 0 && fileData.totalEdits === 0 && fileData.totalWrites === 0) {
|
589
|
+
badges.push(`<span class="file-badge badge-read">👁️ ${fileData.totalReads}</span>`);
|
590
|
+
}
|
591
|
+
|
592
|
+
const timestamp = new Date(fileData.lastModified).toLocaleTimeString();
|
593
|
+
|
594
|
+
return `
|
595
|
+
<div class="file-item" data-path="${fileData.path}">
|
596
|
+
<div class="file-info">
|
597
|
+
<span class="file-icon">📄</span>
|
598
|
+
<span class="file-name">${this.escapeHtml(fileData.fileName)}</span>
|
599
|
+
<div class="file-badges">${badges.join('')}</div>
|
600
|
+
</div>
|
601
|
+
<span class="file-timestamp">${timestamp}</span>
|
602
|
+
</div>
|
603
|
+
`;
|
604
|
+
}
|
605
|
+
|
606
|
+
/**
|
607
|
+
* Attach tree event handlers
|
608
|
+
*/
|
609
|
+
attachTreeHandlers() {
|
610
|
+
// Directory headers - toggle expansion
|
611
|
+
document.querySelectorAll('.directory-header').forEach(header => {
|
612
|
+
header.addEventListener('click', (e) => {
|
613
|
+
const group = header.closest('.directory-group');
|
614
|
+
group.classList.toggle('expanded');
|
615
|
+
});
|
616
|
+
});
|
617
|
+
|
618
|
+
// File items - show diff or content
|
619
|
+
document.querySelectorAll('.file-item').forEach(item => {
|
620
|
+
item.addEventListener('click', (e) => {
|
621
|
+
e.stopPropagation();
|
622
|
+
const filePath = item.dataset.path;
|
623
|
+
this.showFileDetails(filePath);
|
624
|
+
});
|
625
|
+
});
|
626
|
+
|
627
|
+
// Auto-expand first directory
|
628
|
+
const firstDir = document.querySelector('.directory-group');
|
629
|
+
if (firstDir) {
|
630
|
+
firstDir.classList.add('expanded');
|
631
|
+
}
|
632
|
+
}
|
633
|
+
|
634
|
+
/**
|
635
|
+
* Show file details (diff or content)
|
636
|
+
* @param {string} filePath - File path
|
637
|
+
*/
|
638
|
+
showFileDetails(filePath) {
|
639
|
+
const fileData = this.fileTracker.getFileDetails(filePath);
|
640
|
+
if (!fileData) return;
|
641
|
+
|
642
|
+
// If file has edits or writes, show diff
|
643
|
+
if (fileData.totalEdits > 0 || fileData.totalWrites > 0) {
|
644
|
+
const diffData = this.fileTracker.getFileDiff(filePath);
|
645
|
+
if (diffData) {
|
646
|
+
this.diffViewer.show(diffData);
|
647
|
+
}
|
648
|
+
} else {
|
649
|
+
// For read-only files, could show content in a simple viewer
|
650
|
+
console.log('Read-only file:', filePath);
|
651
|
+
// Could implement a simple content viewer here
|
652
|
+
}
|
653
|
+
}
|
654
|
+
|
655
|
+
/**
|
656
|
+
* Update statistics
|
657
|
+
*/
|
658
|
+
updateStatistics() {
|
659
|
+
const stats = this.fileTracker.getStatistics();
|
660
|
+
|
661
|
+
document.getElementById('total-files').textContent = stats.totalFiles;
|
662
|
+
document.getElementById('total-edits').textContent =
|
663
|
+
Array.from(this.fileTracker.fileChanges.values())
|
664
|
+
.reduce((sum, f) => sum + f.totalEdits, 0);
|
665
|
+
document.getElementById('total-writes').textContent =
|
666
|
+
Array.from(this.fileTracker.fileChanges.values())
|
667
|
+
.reduce((sum, f) => sum + f.totalWrites, 0);
|
668
|
+
}
|
669
|
+
|
670
|
+
/**
|
671
|
+
* Escape HTML for safe display
|
672
|
+
* @param {string} text - Text to escape
|
673
|
+
* @returns {string} Escaped text
|
674
|
+
*/
|
675
|
+
escapeHtml(text) {
|
676
|
+
const div = document.createElement('div');
|
677
|
+
div.textContent = text || '';
|
678
|
+
return div.innerHTML;
|
679
|
+
}
|
680
|
+
}
|
681
|
+
|
682
|
+
// Create singleton instance
|
683
|
+
const fileChangeViewer = new FileChangeViewer();
|
684
|
+
|
685
|
+
// Export for use in other modules
|
686
|
+
if (typeof window !== 'undefined') {
|
687
|
+
window.FileChangeViewer = fileChangeViewer;
|
688
|
+
}
|
689
|
+
|
690
|
+
export default fileChangeViewer;
|