claude-autopm 3.22.1 → 3.23.0
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.
|
@@ -252,10 +252,12 @@ function getProjectDiagrams() {
|
|
|
252
252
|
for (const f of entries) {
|
|
253
253
|
if (!f.endsWith('.mmd')) continue;
|
|
254
254
|
try {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
255
|
+
const name = f.replace(/\.mmd$/, '');
|
|
256
|
+
const d = { name, content: fs.readFileSync(path.join(diagramsDir, f), 'utf8') };
|
|
257
|
+
// Load metadata if available
|
|
258
|
+
const metaPath = path.join(diagramsDir, name + '.meta.json');
|
|
259
|
+
try { const meta = JSON.parse(fs.readFileSync(metaPath, 'utf8')); d.type = meta.type || 'custom'; } catch { d.type = 'custom'; }
|
|
260
|
+
diagrams.push(d);
|
|
259
261
|
} catch { /* skip unreadable */ }
|
|
260
262
|
}
|
|
261
263
|
return diagrams;
|
|
@@ -288,6 +290,7 @@ function renderHTML() {
|
|
|
288
290
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
289
291
|
<title>AutoPM Config Dashboard</title>
|
|
290
292
|
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"><\/script>
|
|
293
|
+
<script src="https://cdn.jsdelivr.net/npm/panzoom@9.4.3/dist/panzoom.min.js"><\/script>
|
|
291
294
|
<style>
|
|
292
295
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
293
296
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0d1117; color: #c9d1d9; padding: 24px; }
|
|
@@ -365,42 +368,84 @@ function renderHTML() {
|
|
|
365
368
|
.toast.ok { background: #238636; }
|
|
366
369
|
.toast.err { background: #da3633; }
|
|
367
370
|
footer { margin-top: 24px; text-align: center; color: #484f58; font-size: 12px; }
|
|
368
|
-
/* Diagram
|
|
369
|
-
.diagram-
|
|
370
|
-
.diagram-
|
|
371
|
-
|
|
371
|
+
/* Diagram list */
|
|
372
|
+
.diagram-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; }
|
|
373
|
+
.diagram-grid-item {
|
|
374
|
+
background: #0d1117; border: 1px solid #30363d; border-radius: 8px; padding: 16px; cursor: pointer; transition: border-color 0.15s;
|
|
375
|
+
}
|
|
376
|
+
.diagram-grid-item:hover { border-color: #58a6ff; }
|
|
377
|
+
.diagram-grid-item h4 { color: #58a6ff; margin-bottom: 4px; }
|
|
378
|
+
.diagram-grid-item .diagram-meta { color: #8b949e; font-size: 11px; }
|
|
379
|
+
.diagram-grid-item .diagram-preview { height: 100px; overflow: hidden; opacity: 0.6; margin-top: 8px; }
|
|
380
|
+
.diagram-grid-item .diagram-preview svg { max-height: 100px; width: 100%; }
|
|
381
|
+
/* Diagram split editor */
|
|
382
|
+
.diagram-editor { display: none; }
|
|
383
|
+
.diagram-editor.active { display: flex; flex-direction: column; }
|
|
384
|
+
.diagram-editor-toolbar {
|
|
385
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
386
|
+
padding: 8px 0; margin-bottom: 8px;
|
|
387
|
+
}
|
|
388
|
+
.diagram-editor-toolbar .left { display: flex; align-items: center; gap: 12px; }
|
|
389
|
+
.diagram-editor-toolbar .right { display: flex; gap: 6px; }
|
|
390
|
+
.diagram-editor-toolbar button {
|
|
372
391
|
background: #21262d; color: #8b949e; border: 1px solid #30363d;
|
|
373
|
-
padding:
|
|
392
|
+
padding: 5px 12px; border-radius: 4px; cursor: pointer; font-size: 12px;
|
|
374
393
|
}
|
|
375
|
-
.diagram-
|
|
376
|
-
.diagram-
|
|
377
|
-
.diagram-
|
|
378
|
-
|
|
379
|
-
.diagram-
|
|
380
|
-
display:
|
|
381
|
-
|
|
394
|
+
.diagram-editor-toolbar button:hover { background: #30363d; color: #c9d1d9; }
|
|
395
|
+
.diagram-editor-toolbar .btn-save { background: #238636; color: #fff; border-color: #238636; }
|
|
396
|
+
.diagram-editor-toolbar .btn-save:hover { background: #2ea043; }
|
|
397
|
+
.diagram-editor-toolbar .btn-back { color: #58a6ff; }
|
|
398
|
+
.diagram-split {
|
|
399
|
+
display: flex; gap: 0; flex: 1; min-height: 500px;
|
|
400
|
+
border: 1px solid #30363d; border-radius: 8px; overflow: hidden;
|
|
382
401
|
}
|
|
383
|
-
.diagram-
|
|
384
|
-
|
|
385
|
-
background: #161b22; border: 1px solid #30363d; border-radius: 12px;
|
|
386
|
-
width: 95vw; height: 92vh; display: flex; flex-direction: column; position: relative;
|
|
402
|
+
.diagram-split .code-pane {
|
|
403
|
+
flex: 1; display: flex; flex-direction: column; border-right: 1px solid #30363d; min-width: 0;
|
|
387
404
|
}
|
|
388
|
-
.diagram-
|
|
389
|
-
|
|
390
|
-
|
|
405
|
+
.diagram-split .code-pane .pane-header {
|
|
406
|
+
background: #161b22; padding: 6px 12px; font-size: 11px; color: #8b949e;
|
|
407
|
+
border-bottom: 1px solid #30363d; display: flex; justify-content: space-between; align-items: center;
|
|
408
|
+
}
|
|
409
|
+
.diagram-split .code-pane textarea {
|
|
410
|
+
flex: 1; background: #0d1117; color: #c9d1d9; border: none; padding: 12px;
|
|
411
|
+
font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace; font-size: 13px;
|
|
412
|
+
line-height: 1.5; resize: none; outline: none; tab-size: 2;
|
|
413
|
+
}
|
|
414
|
+
.diagram-split .preview-pane {
|
|
415
|
+
flex: 1; display: flex; flex-direction: column; background: #161b22; min-width: 0;
|
|
416
|
+
}
|
|
417
|
+
.diagram-split .preview-pane .pane-header {
|
|
418
|
+
background: #161b22; padding: 6px 12px; font-size: 11px; color: #8b949e;
|
|
419
|
+
border-bottom: 1px solid #30363d; display: flex; justify-content: space-between; align-items: center;
|
|
420
|
+
}
|
|
421
|
+
.diagram-split .preview-pane .pane-header button {
|
|
422
|
+
background: none; border: none; color: #8b949e; cursor: pointer; font-size: 11px; padding: 2px 6px;
|
|
423
|
+
}
|
|
424
|
+
.diagram-split .preview-pane .pane-header button:hover { color: #c9d1d9; }
|
|
425
|
+
.diagram-split .preview-container {
|
|
426
|
+
flex: 1; overflow: hidden; position: relative; background: #0d1117;
|
|
391
427
|
}
|
|
392
|
-
.diagram-
|
|
393
|
-
.
|
|
394
|
-
|
|
428
|
+
.diagram-split .preview-container svg { width: 100%; height: 100%; }
|
|
429
|
+
.panzoom-hint {
|
|
430
|
+
position: absolute; bottom: 8px; right: 12px; color: #484f58; font-size: 10px; pointer-events: none; z-index: 1;
|
|
431
|
+
}
|
|
432
|
+
/* Diagram card actions */
|
|
433
|
+
.diagram-grid-item .card-actions {
|
|
434
|
+
display: flex; gap: 6px; margin-top: 8px; border-top: 1px solid #21262d; padding-top: 8px;
|
|
435
|
+
}
|
|
436
|
+
.diagram-grid-item .card-actions button {
|
|
395
437
|
background: #21262d; color: #8b949e; border: 1px solid #30363d;
|
|
396
|
-
padding:
|
|
438
|
+
padding: 3px 10px; border-radius: 4px; cursor: pointer; font-size: 11px;
|
|
397
439
|
}
|
|
398
|
-
.diagram-
|
|
399
|
-
.diagram-
|
|
400
|
-
|
|
401
|
-
|
|
440
|
+
.diagram-grid-item .card-actions button:hover { background: #30363d; color: #c9d1d9; }
|
|
441
|
+
.diagram-grid-item .card-actions .btn-remove { color: #f85149; }
|
|
442
|
+
.diagram-grid-item .card-actions .btn-remove:hover { background: #3d1116; border-color: #f85149; }
|
|
443
|
+
/* Fullscreen editor */
|
|
444
|
+
.diagram-editor.fullscreen {
|
|
445
|
+
position: fixed; inset: 0; z-index: 998; background: #0d1117;
|
|
446
|
+
padding: 12px; display: flex; flex-direction: column;
|
|
402
447
|
}
|
|
403
|
-
.diagram-
|
|
448
|
+
.diagram-editor.fullscreen .diagram-split { min-height: 0; flex: 1; }
|
|
404
449
|
</style>
|
|
405
450
|
</head>
|
|
406
451
|
<body>
|
|
@@ -531,30 +576,63 @@ function renderHTML() {
|
|
|
531
576
|
|
|
532
577
|
<!-- Diagrams Tab -->
|
|
533
578
|
<div id="tab-diagrams" class="tab-content">
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
<div
|
|
537
|
-
<
|
|
538
|
-
<button onclick="
|
|
579
|
+
<!-- Diagram list view -->
|
|
580
|
+
<div id="diagram-list-view">
|
|
581
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
|
|
582
|
+
<p class="desc" style="margin:0;">Project diagrams stored in <code>.claude/pm/diagrams/</code></p>
|
|
583
|
+
<button class="btn" onclick="showNewDiagramForm()">+ New Diagram</button>
|
|
539
584
|
</div>
|
|
540
|
-
|
|
541
|
-
|
|
585
|
+
<!-- New diagram form (hidden by default) -->
|
|
586
|
+
<div id="new-diagram-form" class="card" style="display:none;margin-bottom:16px;">
|
|
587
|
+
<div class="row">
|
|
588
|
+
<div><label>Name (slug)</label><input type="text" id="new-diagram-name" placeholder="e.g. frontend, backend, database"></div>
|
|
589
|
+
<div><label>Type</label><select id="new-diagram-type">
|
|
590
|
+
<option value="architecture">architecture</option>
|
|
591
|
+
<option value="modules">modules</option>
|
|
592
|
+
<option value="data-flow">data-flow</option>
|
|
593
|
+
<option value="dependencies">dependencies</option>
|
|
594
|
+
<option value="custom">custom</option>
|
|
595
|
+
</select></div>
|
|
596
|
+
</div>
|
|
597
|
+
<label>Description</label>
|
|
598
|
+
<input type="text" id="new-diagram-desc" placeholder="e.g. Frontend React component tree">
|
|
599
|
+
<div style="margin-top:12px;">
|
|
600
|
+
<button class="btn" onclick="createNewDiagram()">Create</button>
|
|
601
|
+
<button class="btn" style="background:#21262d;margin-left:6px;" onclick="hideNewDiagramForm()">Cancel</button>
|
|
602
|
+
</div>
|
|
542
603
|
</div>
|
|
604
|
+
<div id="diagram-grid" class="diagram-grid"></div>
|
|
543
605
|
</div>
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
<div class="
|
|
553
|
-
<button onclick="
|
|
554
|
-
<button onclick="
|
|
606
|
+
<!-- Diagram editor view (split: code | preview) -->
|
|
607
|
+
<div id="diagram-editor-view" class="diagram-editor">
|
|
608
|
+
<div class="diagram-editor-toolbar">
|
|
609
|
+
<div class="left">
|
|
610
|
+
<button class="btn-back" onclick="closeDiagramEditor()">← Back</button>
|
|
611
|
+
<strong id="editor-diagram-name" style="color:#c9d1d9;font-size:14px;"></strong>
|
|
612
|
+
<span id="editor-unsaved" style="color:#d29922;font-size:11px;display:none;">● unsaved</span>
|
|
613
|
+
</div>
|
|
614
|
+
<div class="right">
|
|
615
|
+
<button onclick="downloadCurrentDiagram()">↓ .mmd</button>
|
|
616
|
+
<button onclick="toggleEditorFullscreen()">⤢ Fullscreen</button>
|
|
617
|
+
<button class="btn-save" onclick="saveDiagram()">Save</button>
|
|
618
|
+
</div>
|
|
619
|
+
</div>
|
|
620
|
+
<div class="diagram-split" id="diagram-split">
|
|
621
|
+
<div class="code-pane">
|
|
622
|
+
<div class="pane-header"><span>Mermaid Source</span></div>
|
|
623
|
+
<textarea id="diagram-code" spellcheck="false"></textarea>
|
|
624
|
+
</div>
|
|
625
|
+
<div class="preview-pane">
|
|
626
|
+
<div class="pane-header">
|
|
627
|
+
<span>Preview</span>
|
|
628
|
+
<button onclick="resetPanZoom()">Reset zoom</button>
|
|
629
|
+
</div>
|
|
630
|
+
<div class="preview-container" id="diagram-preview-container">
|
|
631
|
+
<div id="diagram-preview-render"></div>
|
|
632
|
+
<div class="panzoom-hint">Scroll to zoom · Drag to pan</div>
|
|
633
|
+
</div>
|
|
555
634
|
</div>
|
|
556
635
|
</div>
|
|
557
|
-
<div class="modal-body" id="modal-diagram-body"></div>
|
|
558
636
|
</div>
|
|
559
637
|
</div>
|
|
560
638
|
|
|
@@ -589,140 +667,235 @@ const headers = { 'Authorization': 'Bearer ' + TOKEN, 'Content-Type': 'applicati
|
|
|
589
667
|
function esc(s) { return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); }
|
|
590
668
|
|
|
591
669
|
function showTab(name, btn) {
|
|
670
|
+
// Close diagram editor if leaving diagrams tab
|
|
671
|
+
if (currentDiagramName && name !== 'diagrams') closeDiagramEditor();
|
|
592
672
|
document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
|
|
593
673
|
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
594
674
|
document.getElementById('tab-' + name).classList.add('active');
|
|
595
675
|
btn.classList.add('active');
|
|
596
|
-
if (name === 'diagrams')
|
|
676
|
+
if (name === 'diagrams') renderDiagramsTab();
|
|
677
|
+
// Pause auto-refresh on tabs with editable forms
|
|
678
|
+
editingLock = (name === 'mcp' || name === 'diagrams');
|
|
597
679
|
}
|
|
598
680
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
let
|
|
681
|
+
// --- Diagram editor state ---
|
|
682
|
+
let diagramsLoaded = false;
|
|
683
|
+
let allDiagrams = [];
|
|
684
|
+
let currentDiagramName = null;
|
|
685
|
+
let currentDiagramSaved = '';
|
|
686
|
+
let panzoomInstance = null;
|
|
687
|
+
let previewDebounce = null;
|
|
603
688
|
|
|
604
|
-
function
|
|
605
|
-
if (
|
|
689
|
+
function renderDiagramsTab() {
|
|
690
|
+
if (diagramsLoaded) return;
|
|
606
691
|
fetch('/api/diagrams', { headers: { 'Authorization': 'Bearer ' + TOKEN } })
|
|
607
692
|
.then(r => r.json())
|
|
608
693
|
.then(data => {
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
epicCard.style.display = '';
|
|
614
|
-
epicEl.className = 'mermaid';
|
|
615
|
-
epicEl.textContent = data.epicFlow;
|
|
616
|
-
diagramSources['diagram-epic'] = data.epicFlow;
|
|
617
|
-
diagramNames['diagram-epic'] = 'epic-flow';
|
|
618
|
-
} else {
|
|
619
|
-
epicCard.style.display = 'none';
|
|
620
|
-
epicEl.className = '';
|
|
621
|
-
epicEl.textContent = '';
|
|
622
|
-
}
|
|
623
|
-
document.getElementById('diagram-plugins').textContent = data.pluginGraph;
|
|
624
|
-
document.getElementById('diagram-agents').textContent = data.agentTree;
|
|
625
|
-
diagramSources['diagram-plugins'] = data.pluginGraph;
|
|
626
|
-
diagramSources['diagram-agents'] = data.agentTree;
|
|
627
|
-
diagramNames['diagram-plugins'] = 'plugins';
|
|
628
|
-
diagramNames['diagram-agents'] = 'agents';
|
|
629
|
-
// Render project diagrams dynamically
|
|
630
|
-
const container = document.getElementById('project-diagrams');
|
|
631
|
-
container.innerHTML = '';
|
|
632
|
-
if (data.projectDiagrams && data.projectDiagrams.length > 0) {
|
|
633
|
-
data.projectDiagrams.forEach((d, i) => {
|
|
634
|
-
const id = 'proj-diagram-' + i;
|
|
635
|
-
diagramSources[id] = d.content;
|
|
636
|
-
diagramNames[id] = d.name;
|
|
637
|
-
const card = document.createElement('div');
|
|
638
|
-
card.className = 'card diagram-card';
|
|
639
|
-
const h3 = document.createElement('h3');
|
|
640
|
-
h3.textContent = d.name;
|
|
641
|
-
card.appendChild(h3);
|
|
642
|
-
const toolbar = document.createElement('div');
|
|
643
|
-
toolbar.className = 'diagram-toolbar';
|
|
644
|
-
const dlBtn = document.createElement('button');
|
|
645
|
-
dlBtn.textContent = '↓ .mmd';
|
|
646
|
-
dlBtn.onclick = function(e) { e.stopPropagation(); downloadMermaid(id); };
|
|
647
|
-
const fsBtn = document.createElement('button');
|
|
648
|
-
fsBtn.textContent = '⤢ Fullscreen';
|
|
649
|
-
fsBtn.onclick = function(e) { e.stopPropagation(); openDiagramModal(id, d.name); };
|
|
650
|
-
toolbar.appendChild(dlBtn);
|
|
651
|
-
toolbar.appendChild(fsBtn);
|
|
652
|
-
card.appendChild(toolbar);
|
|
653
|
-
const wrap = document.createElement('div');
|
|
654
|
-
wrap.className = 'mermaid-wrap';
|
|
655
|
-
wrap.onclick = function() { openDiagramModal(id, d.name); };
|
|
656
|
-
const pre = document.createElement('pre');
|
|
657
|
-
pre.className = 'mermaid';
|
|
658
|
-
pre.id = id;
|
|
659
|
-
pre.textContent = d.content;
|
|
660
|
-
wrap.appendChild(pre);
|
|
661
|
-
card.appendChild(wrap);
|
|
662
|
-
container.appendChild(card);
|
|
663
|
-
});
|
|
664
|
-
} else {
|
|
665
|
-
const msg = document.createElement('div');
|
|
666
|
-
msg.className = 'card';
|
|
667
|
-
msg.innerHTML = '<p class="desc">No project diagrams yet. Run <code>/pm:diagram-new</code> to create your first diagram.</p>';
|
|
668
|
-
container.appendChild(msg);
|
|
669
|
-
}
|
|
670
|
-
if (typeof mermaid !== 'undefined' && mermaid.run) {
|
|
671
|
-
mermaid.run();
|
|
672
|
-
} else {
|
|
673
|
-
const errorMsg = 'Mermaid library unavailable. Check network connection.';
|
|
674
|
-
['diagram-epic','diagram-plugins','diagram-agents'].forEach(id => {
|
|
675
|
-
const el = document.getElementById(id);
|
|
676
|
-
if (el) el.textContent = errorMsg;
|
|
677
|
-
});
|
|
678
|
-
document.querySelectorAll('#project-diagrams .mermaid').forEach(el => {
|
|
679
|
-
el.textContent = errorMsg;
|
|
694
|
+
allDiagrams = [];
|
|
695
|
+
if (data.projectDiagrams) {
|
|
696
|
+
data.projectDiagrams.forEach(d => {
|
|
697
|
+
allDiagrams.push({ name: d.name, content: d.content, type: d.type || 'custom' });
|
|
680
698
|
});
|
|
681
699
|
}
|
|
682
|
-
|
|
700
|
+
renderDiagramGrid();
|
|
701
|
+
diagramsLoaded = true;
|
|
683
702
|
});
|
|
684
703
|
}
|
|
685
704
|
|
|
686
|
-
function
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
705
|
+
function renderDiagramGrid() {
|
|
706
|
+
const grid = document.getElementById('diagram-grid');
|
|
707
|
+
grid.textContent = '';
|
|
708
|
+
if (allDiagrams.length === 0) {
|
|
709
|
+
const msg = document.createElement('p');
|
|
710
|
+
msg.className = 'desc';
|
|
711
|
+
msg.textContent = 'No diagrams yet. Click "+ New Diagram" or run /pm:diagram-new <name>.';
|
|
712
|
+
grid.appendChild(msg);
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
allDiagrams.forEach(d => {
|
|
716
|
+
const item = document.createElement('div');
|
|
717
|
+
item.className = 'diagram-grid-item';
|
|
718
|
+
item.setAttribute('tabindex', '0');
|
|
719
|
+
const h4 = document.createElement('h4');
|
|
720
|
+
h4.textContent = d.name;
|
|
721
|
+
const meta = document.createElement('div');
|
|
722
|
+
meta.className = 'diagram-meta';
|
|
723
|
+
meta.textContent = d.type;
|
|
724
|
+
const preview = document.createElement('div');
|
|
725
|
+
preview.className = 'diagram-preview';
|
|
693
726
|
const pre = document.createElement('pre');
|
|
694
727
|
pre.className = 'mermaid';
|
|
695
|
-
pre.textContent =
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
728
|
+
pre.textContent = d.content;
|
|
729
|
+
preview.appendChild(pre);
|
|
730
|
+
// Action buttons
|
|
731
|
+
const actions = document.createElement('div');
|
|
732
|
+
actions.className = 'card-actions';
|
|
733
|
+
const editBtn = document.createElement('button');
|
|
734
|
+
editBtn.textContent = 'Edit';
|
|
735
|
+
editBtn.onclick = function(e) { e.stopPropagation(); openDiagramEditor(d.name); };
|
|
736
|
+
const dlBtn = document.createElement('button');
|
|
737
|
+
dlBtn.textContent = '↓ .mmd';
|
|
738
|
+
dlBtn.onclick = function(e) { e.stopPropagation(); downloadDiagram(d.name, d.content); };
|
|
739
|
+
const rmBtn = document.createElement('button');
|
|
740
|
+
rmBtn.textContent = 'Remove';
|
|
741
|
+
rmBtn.className = 'btn-remove';
|
|
742
|
+
rmBtn.onclick = function(e) { e.stopPropagation(); removeDiagram(d.name); };
|
|
743
|
+
actions.appendChild(editBtn);
|
|
744
|
+
actions.appendChild(dlBtn);
|
|
745
|
+
actions.appendChild(rmBtn);
|
|
746
|
+
item.appendChild(h4);
|
|
747
|
+
item.appendChild(meta);
|
|
748
|
+
item.appendChild(preview);
|
|
749
|
+
item.appendChild(actions);
|
|
750
|
+
// Click card body (not actions) opens editor
|
|
751
|
+
item.onclick = function(e) { if (!e.target.closest('.card-actions')) openDiagramEditor(d.name); };
|
|
752
|
+
item.onkeydown = function(e) {
|
|
753
|
+
if (e.target && e.target.closest('.card-actions')) return;
|
|
754
|
+
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openDiagramEditor(d.name); }
|
|
755
|
+
};
|
|
756
|
+
grid.appendChild(item);
|
|
757
|
+
});
|
|
758
|
+
if (typeof mermaid !== 'undefined' && mermaid.run) {
|
|
759
|
+
mermaid.run({ nodes: grid.querySelectorAll('.mermaid') });
|
|
702
760
|
}
|
|
703
|
-
const modal = document.getElementById('diagram-modal');
|
|
704
|
-
modal.classList.add('open');
|
|
705
|
-
modalPrevFocus = document.activeElement;
|
|
706
|
-
const closeBtn = modal.querySelector('.modal-actions button:last-child');
|
|
707
|
-
if (closeBtn) closeBtn.focus();
|
|
708
|
-
document.addEventListener('keydown', modalEscHandler);
|
|
709
761
|
}
|
|
710
762
|
|
|
711
|
-
|
|
712
|
-
function
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
763
|
+
// New diagram form
|
|
764
|
+
function showNewDiagramForm() { document.getElementById('new-diagram-form').style.display = ''; }
|
|
765
|
+
function hideNewDiagramForm() { document.getElementById('new-diagram-form').style.display = 'none'; }
|
|
766
|
+
|
|
767
|
+
async function createNewDiagram() {
|
|
768
|
+
const name = document.getElementById('new-diagram-name').value.trim().replace(/[^a-zA-Z0-9_-]/g, '-');
|
|
769
|
+
if (!name) { toast('Name is required', false); return; }
|
|
770
|
+
if (allDiagrams.find(d => d.name === name)) { toast('Diagram "' + name + '" already exists', false); return; }
|
|
771
|
+
const type = document.getElementById('new-diagram-type').value;
|
|
772
|
+
const desc = document.getElementById('new-diagram-desc').value.trim();
|
|
773
|
+
const template = 'graph TD\\n A[' + esc(desc || name) + ']';
|
|
774
|
+
try {
|
|
775
|
+
await api('POST', '/api/diagrams/' + encodeURIComponent(name), { content: template, type: type, description: desc });
|
|
776
|
+
hideNewDiagramForm();
|
|
777
|
+
document.getElementById('new-diagram-name').value = '';
|
|
778
|
+
document.getElementById('new-diagram-desc').value = '';
|
|
779
|
+
diagramsLoaded = false;
|
|
780
|
+
renderDiagramsTab();
|
|
781
|
+
toast('Diagram "' + name + '" created', true);
|
|
782
|
+
} catch (e) { /* api() already shows toast */ }
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
async function removeDiagram(name) {
|
|
786
|
+
if (!confirm('Remove diagram "' + name + '"?')) return;
|
|
787
|
+
try {
|
|
788
|
+
await api('DELETE', '/api/diagrams/' + encodeURIComponent(name));
|
|
789
|
+
allDiagrams = allDiagrams.filter(d => d.name !== name);
|
|
790
|
+
renderDiagramGrid();
|
|
791
|
+
toast('Diagram removed', true);
|
|
792
|
+
} catch (e) { /* api() already shows toast */ }
|
|
717
793
|
}
|
|
718
794
|
|
|
719
|
-
function
|
|
795
|
+
function downloadDiagram(name, content) {
|
|
796
|
+
const safeName = name.replace(/[^a-zA-Z0-9_-]/g, '_') + '.mmd';
|
|
797
|
+
const blob = new Blob([content], { type: 'text/plain' });
|
|
798
|
+
const a = document.createElement('a');
|
|
799
|
+
const url = URL.createObjectURL(blob);
|
|
800
|
+
a.href = url; a.download = safeName; a.style.display = 'none';
|
|
801
|
+
document.body.appendChild(a);
|
|
802
|
+
try { a.click(); } finally { document.body.removeChild(a); setTimeout(function() { URL.revokeObjectURL(url); }, 100); }
|
|
803
|
+
}
|
|
720
804
|
|
|
721
|
-
function
|
|
722
|
-
const
|
|
723
|
-
if (!
|
|
724
|
-
|
|
725
|
-
|
|
805
|
+
function openDiagramEditor(name) {
|
|
806
|
+
const d = allDiagrams.find(x => x.name === name);
|
|
807
|
+
if (!d) return;
|
|
808
|
+
currentDiagramName = name;
|
|
809
|
+
currentDiagramSaved = d.content;
|
|
810
|
+
document.getElementById('editor-diagram-name').textContent = name;
|
|
811
|
+
document.getElementById('editor-unsaved').style.display = 'none';
|
|
812
|
+
const codeEl = document.getElementById('diagram-code');
|
|
813
|
+
codeEl.value = d.content;
|
|
814
|
+
codeEl.readOnly = false;
|
|
815
|
+
document.getElementById('diagram-list-view').style.display = 'none';
|
|
816
|
+
document.getElementById('diagram-editor-view').classList.add('active');
|
|
817
|
+
renderPreview(d.content);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
function closeDiagramEditor() {
|
|
821
|
+
clearTimeout(previewDebounce); previewDebounce = null;
|
|
822
|
+
if (panzoomInstance) { panzoomInstance.dispose(); panzoomInstance = null; }
|
|
823
|
+
const editor = document.getElementById('diagram-editor-view');
|
|
824
|
+
editor.classList.remove('active', 'fullscreen');
|
|
825
|
+
document.getElementById('diagram-list-view').style.display = '';
|
|
826
|
+
currentDiagramName = null;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
function toggleEditorFullscreen() {
|
|
830
|
+
document.getElementById('diagram-editor-view').classList.toggle('fullscreen');
|
|
831
|
+
// Re-render preview to fit new size
|
|
832
|
+
if (panzoomInstance) { panzoomInstance.dispose(); panzoomInstance = null; }
|
|
833
|
+
setTimeout(function() { renderPreview(document.getElementById('diagram-code').value); }, 100);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
function renderPreview(source) {
|
|
837
|
+
const container = document.getElementById('diagram-preview-render');
|
|
838
|
+
container.textContent = '';
|
|
839
|
+
if (panzoomInstance) { panzoomInstance.dispose(); panzoomInstance = null; }
|
|
840
|
+
if (!source || !source.trim()) { container.textContent = 'Empty diagram'; return; }
|
|
841
|
+
const pre = document.createElement('pre');
|
|
842
|
+
pre.className = 'mermaid';
|
|
843
|
+
pre.textContent = source;
|
|
844
|
+
container.appendChild(pre);
|
|
845
|
+
if (typeof mermaid !== 'undefined' && mermaid.run) {
|
|
846
|
+
mermaid.run({ nodes: [pre] }).then(function() {
|
|
847
|
+
const svg = container.querySelector('svg');
|
|
848
|
+
if (svg && typeof panzoom !== 'undefined') {
|
|
849
|
+
svg.style.width = '100%';
|
|
850
|
+
svg.style.height = '100%';
|
|
851
|
+
panzoomInstance = panzoom(svg, { maxZoom: 10, minZoom: 0.1, smoothScroll: false });
|
|
852
|
+
}
|
|
853
|
+
}).catch(function() {
|
|
854
|
+
container.textContent = 'Mermaid syntax error — check your code';
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function resetPanZoom() {
|
|
860
|
+
if (panzoomInstance) {
|
|
861
|
+
panzoomInstance.moveTo(0, 0);
|
|
862
|
+
panzoomInstance.zoomAbs(0, 0, 1);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Live preview on code change
|
|
867
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
868
|
+
const codeEl = document.getElementById('diagram-code');
|
|
869
|
+
if (codeEl) {
|
|
870
|
+
codeEl.addEventListener('input', function() {
|
|
871
|
+
document.getElementById('editor-unsaved').style.display = codeEl.value !== currentDiagramSaved ? '' : 'none';
|
|
872
|
+
clearTimeout(previewDebounce);
|
|
873
|
+
previewDebounce = setTimeout(function() { renderPreview(codeEl.value); }, 600);
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
async function saveDiagram() {
|
|
879
|
+
if (!currentDiagramName) return;
|
|
880
|
+
const d = allDiagrams.find(x => x.name === currentDiagramName);
|
|
881
|
+
const content = document.getElementById('diagram-code').value;
|
|
882
|
+
try {
|
|
883
|
+
await api('POST', '/api/diagrams/' + encodeURIComponent(currentDiagramName), { content: content });
|
|
884
|
+
currentDiagramSaved = content;
|
|
885
|
+
if (d) d.content = content;
|
|
886
|
+
document.getElementById('editor-unsaved').style.display = 'none';
|
|
887
|
+
// Re-render grid so mini previews reflect saved content
|
|
888
|
+
diagramsLoaded = false;
|
|
889
|
+
toast('Diagram saved', true);
|
|
890
|
+
} catch (e) {
|
|
891
|
+
/* api() already shows toast */
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function downloadCurrentDiagram() {
|
|
896
|
+
const src = document.getElementById('diagram-code').value;
|
|
897
|
+
if (!src || !currentDiagramName) return;
|
|
898
|
+
const safeName = currentDiagramName.replace(/[^a-zA-Z0-9_-]/g, '_') + '.mmd';
|
|
726
899
|
const blob = new Blob([src], { type: 'text/plain' });
|
|
727
900
|
const a = document.createElement('a');
|
|
728
901
|
const url = URL.createObjectURL(blob);
|
|
@@ -1113,19 +1286,8 @@ function addEnvSuggestion(key, val, hint) {
|
|
|
1113
1286
|
addEnvRow(key, val);
|
|
1114
1287
|
}
|
|
1115
1288
|
|
|
1116
|
-
// Pause auto-refresh while
|
|
1289
|
+
// Pause auto-refresh while MCP & Keys tab is active (editing forms)
|
|
1117
1290
|
let editingLock = false;
|
|
1118
|
-
document.addEventListener('focusin', function(e) {
|
|
1119
|
-
if (e.target.closest('#mcp-list, #env-list, .mcp-entry, .env-row')) editingLock = true;
|
|
1120
|
-
});
|
|
1121
|
-
document.addEventListener('focusout', function(e) {
|
|
1122
|
-
if (e.target.closest('#mcp-list, #env-list, .mcp-entry, .env-row')) {
|
|
1123
|
-
setTimeout(function() {
|
|
1124
|
-
const active = document.activeElement;
|
|
1125
|
-
if (!active || !active.closest('#mcp-list, #env-list, .mcp-entry, .env-row')) editingLock = false;
|
|
1126
|
-
}, 200);
|
|
1127
|
-
}
|
|
1128
|
-
});
|
|
1129
1291
|
|
|
1130
1292
|
async function refresh() {
|
|
1131
1293
|
if (editingLock) return;
|
|
@@ -1195,6 +1357,43 @@ const server = http.createServer(async (req, res) => {
|
|
|
1195
1357
|
return json(res, 200, getDiagramsData());
|
|
1196
1358
|
}
|
|
1197
1359
|
|
|
1360
|
+
// Save diagram content
|
|
1361
|
+
const diagramMatch = parsedUrl.pathname.match(/^\/api\/diagrams\/([a-zA-Z0-9_-]+)$/);
|
|
1362
|
+
if (req.method === 'POST' && diagramMatch) {
|
|
1363
|
+
const name = decodeURIComponent(diagramMatch[1]);
|
|
1364
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(name)) return json(res, 400, { error: 'Invalid diagram name' });
|
|
1365
|
+
const { body, err } = await parseJSONBody(req, res);
|
|
1366
|
+
if (err) return;
|
|
1367
|
+
if (typeof body.content !== 'string') return json(res, 400, { error: 'content must be a string' });
|
|
1368
|
+
if (body.content.length > 500000) return json(res, 400, { error: 'content too large (max 500KB)' });
|
|
1369
|
+
const diagramsDir = path.join(basePath, '.claude', 'pm', 'diagrams');
|
|
1370
|
+
if (!fs.existsSync(diagramsDir)) fs.mkdirSync(diagramsDir, { recursive: true });
|
|
1371
|
+
fs.writeFileSync(path.join(diagramsDir, name + '.mmd'), body.content, 'utf8');
|
|
1372
|
+
// Update metadata timestamp if exists, or create
|
|
1373
|
+
const metaPath = path.join(diagramsDir, name + '.meta.json');
|
|
1374
|
+
let meta = {};
|
|
1375
|
+
try { meta = JSON.parse(fs.readFileSync(metaPath, 'utf8')); } catch {}
|
|
1376
|
+
meta.name = meta.name || name;
|
|
1377
|
+
if (typeof body.type === 'string') meta.type = body.type.slice(0, 50);
|
|
1378
|
+
if (typeof body.description === 'string') meta.description = body.description.slice(0, 500);
|
|
1379
|
+
meta.updated = new Date().toISOString().replace(/\\.\\d{3}Z$/, 'Z');
|
|
1380
|
+
if (!meta.created) meta.created = meta.updated;
|
|
1381
|
+
fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2), 'utf8');
|
|
1382
|
+
return json(res, 200, { ok: true });
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
const diagramDeleteMatch = parsedUrl.pathname.match(/^\/api\/diagrams\/([a-zA-Z0-9_-]+)$/);
|
|
1386
|
+
if (req.method === 'DELETE' && diagramDeleteMatch) {
|
|
1387
|
+
const name = decodeURIComponent(diagramDeleteMatch[1]);
|
|
1388
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(name)) return json(res, 400, { error: 'Invalid diagram name' });
|
|
1389
|
+
const diagramsDir = path.join(basePath, '.claude', 'pm', 'diagrams');
|
|
1390
|
+
const mmdPath = path.join(diagramsDir, name + '.mmd');
|
|
1391
|
+
const metaPath = path.join(diagramsDir, name + '.meta.json');
|
|
1392
|
+
try { fs.unlinkSync(mmdPath); } catch {}
|
|
1393
|
+
try { fs.unlinkSync(metaPath); } catch {}
|
|
1394
|
+
return json(res, 200, { ok: true });
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1198
1397
|
if (req.method === 'GET' && parsedUrl.pathname === '/api/tests') {
|
|
1199
1398
|
return json(res, 200, getTestsData());
|
|
1200
1399
|
}
|
package/package.json
CHANGED