claude-autopm 3.22.1 → 3.22.2
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,67 @@ 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;
|
|
391
408
|
}
|
|
392
|
-
.diagram-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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;
|
|
397
420
|
}
|
|
398
|
-
.diagram-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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;
|
|
427
|
+
}
|
|
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;
|
|
402
431
|
}
|
|
403
|
-
.diagram-modal .modal-body svg { max-width: 100%; height: auto; }
|
|
404
432
|
</style>
|
|
405
433
|
</head>
|
|
406
434
|
<body>
|
|
@@ -531,30 +559,43 @@ function renderHTML() {
|
|
|
531
559
|
|
|
532
560
|
<!-- Diagrams Tab -->
|
|
533
561
|
<div id="tab-diagrams" class="tab-content">
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
<div
|
|
537
|
-
<
|
|
538
|
-
<button onclick="openDiagramModal('diagram-epic','Epic Flow')">⤢ Fullscreen</button>
|
|
539
|
-
</div>
|
|
540
|
-
<div class="mermaid-wrap" onclick="openDiagramModal('diagram-epic','Epic Flow')">
|
|
541
|
-
<pre class="mermaid" id="diagram-epic"></pre>
|
|
562
|
+
<!-- Diagram list view -->
|
|
563
|
+
<div id="diagram-list-view">
|
|
564
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
|
|
565
|
+
<p class="desc" style="margin:0;">Click a diagram to view and edit. Create new: <code>/pm:diagram-new <name></code></p>
|
|
542
566
|
</div>
|
|
567
|
+
<div id="diagram-grid" class="diagram-grid"></div>
|
|
543
568
|
</div>
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
<button onclick="
|
|
569
|
+
<!-- Diagram editor view (split: code | preview) -->
|
|
570
|
+
<div id="diagram-editor-view" class="diagram-editor">
|
|
571
|
+
<div class="diagram-editor-toolbar">
|
|
572
|
+
<div class="left">
|
|
573
|
+
<button class="btn-back" onclick="closeDiagramEditor()">← Back</button>
|
|
574
|
+
<strong id="editor-diagram-name" style="color:#c9d1d9;font-size:14px;"></strong>
|
|
575
|
+
<span id="editor-diagram-meta" style="color:#8b949e;font-size:11px;"></span>
|
|
576
|
+
<span id="editor-unsaved" style="color:#d29922;font-size:11px;display:none;">● unsaved</span>
|
|
577
|
+
</div>
|
|
578
|
+
<div class="right">
|
|
579
|
+
<button onclick="downloadCurrentDiagram()">↓ .mmd</button>
|
|
580
|
+
<button class="btn-save" onclick="saveDiagram()">Save</button>
|
|
581
|
+
</div>
|
|
582
|
+
</div>
|
|
583
|
+
<div class="diagram-split">
|
|
584
|
+
<div class="code-pane">
|
|
585
|
+
<div class="pane-header"><span>Mermaid Source</span></div>
|
|
586
|
+
<textarea id="diagram-code" spellcheck="false"></textarea>
|
|
587
|
+
</div>
|
|
588
|
+
<div class="preview-pane">
|
|
589
|
+
<div class="pane-header">
|
|
590
|
+
<span>Preview</span>
|
|
591
|
+
<button onclick="resetPanZoom()">Reset zoom</button>
|
|
592
|
+
</div>
|
|
593
|
+
<div class="preview-container" id="diagram-preview-container">
|
|
594
|
+
<div id="diagram-preview-render"></div>
|
|
595
|
+
<div class="panzoom-hint">Scroll to zoom · Drag to pan</div>
|
|
596
|
+
</div>
|
|
555
597
|
</div>
|
|
556
598
|
</div>
|
|
557
|
-
<div class="modal-body" id="modal-diagram-body"></div>
|
|
558
599
|
</div>
|
|
559
600
|
</div>
|
|
560
601
|
|
|
@@ -589,140 +630,171 @@ const headers = { 'Authorization': 'Bearer ' + TOKEN, 'Content-Type': 'applicati
|
|
|
589
630
|
function esc(s) { return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); }
|
|
590
631
|
|
|
591
632
|
function showTab(name, btn) {
|
|
633
|
+
// Close diagram editor if leaving diagrams tab
|
|
634
|
+
if (currentDiagramName && name !== 'diagrams') closeDiagramEditor();
|
|
592
635
|
document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
|
|
593
636
|
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
594
637
|
document.getElementById('tab-' + name).classList.add('active');
|
|
595
638
|
btn.classList.add('active');
|
|
596
|
-
if (name === 'diagrams')
|
|
639
|
+
if (name === 'diagrams') renderDiagramsTab();
|
|
640
|
+
// Pause auto-refresh on tabs with editable forms
|
|
641
|
+
editingLock = (name === 'mcp' || name === 'diagrams');
|
|
597
642
|
}
|
|
598
643
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
let
|
|
644
|
+
// --- Diagram editor state ---
|
|
645
|
+
let diagramsLoaded = false;
|
|
646
|
+
let allDiagrams = [];
|
|
647
|
+
let currentDiagramName = null;
|
|
648
|
+
let currentDiagramSaved = '';
|
|
649
|
+
let panzoomInstance = null;
|
|
650
|
+
let previewDebounce = null;
|
|
603
651
|
|
|
604
|
-
function
|
|
605
|
-
if (
|
|
652
|
+
function renderDiagramsTab() {
|
|
653
|
+
if (diagramsLoaded) return;
|
|
606
654
|
fetch('/api/diagrams', { headers: { 'Authorization': 'Bearer ' + TOKEN } })
|
|
607
655
|
.then(r => r.json())
|
|
608
656
|
.then(data => {
|
|
609
|
-
//
|
|
610
|
-
|
|
611
|
-
const epicEl = document.getElementById('diagram-epic');
|
|
657
|
+
// Merge system-generated + project diagrams into one list
|
|
658
|
+
allDiagrams = [];
|
|
612
659
|
if (data.epicFlowHasData) {
|
|
613
|
-
|
|
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 = '';
|
|
660
|
+
allDiagrams.push({ name: 'epic-flow', content: data.epicFlow, type: 'system', meta: 'PRD → Epic → Tasks' });
|
|
622
661
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
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);
|
|
662
|
+
allDiagrams.push({ name: 'plugins', content: data.pluginGraph, type: 'system', meta: 'Plugin dependencies' });
|
|
663
|
+
allDiagrams.push({ name: 'agents', content: data.agentTree, type: 'system', meta: 'Agent selection tree' });
|
|
664
|
+
if (data.projectDiagrams) {
|
|
665
|
+
data.projectDiagrams.forEach(d => {
|
|
666
|
+
allDiagrams.push({ name: d.name, content: d.content, type: 'project', meta: d.type || 'custom' });
|
|
663
667
|
});
|
|
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
668
|
}
|
|
670
|
-
|
|
671
|
-
|
|
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;
|
|
680
|
-
});
|
|
681
|
-
}
|
|
682
|
-
diagramsRendered = true;
|
|
669
|
+
renderDiagramGrid();
|
|
670
|
+
diagramsLoaded = true;
|
|
683
671
|
});
|
|
684
672
|
}
|
|
685
673
|
|
|
686
|
-
function
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
674
|
+
function renderDiagramGrid() {
|
|
675
|
+
const grid = document.getElementById('diagram-grid');
|
|
676
|
+
grid.textContent = '';
|
|
677
|
+
if (allDiagrams.length === 0) {
|
|
678
|
+
grid.innerHTML = '<p class="desc">No diagrams yet. Run <code>/pm:diagram-new <name></code> to create one.</p>';
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
allDiagrams.forEach(d => {
|
|
682
|
+
const item = document.createElement('div');
|
|
683
|
+
item.className = 'diagram-grid-item';
|
|
684
|
+
item.setAttribute('role', 'button');
|
|
685
|
+
item.setAttribute('tabindex', '0');
|
|
686
|
+
item.onclick = function() { openDiagramEditor(d.name); };
|
|
687
|
+
item.onkeydown = function(e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openDiagramEditor(d.name); } };
|
|
688
|
+
const h4 = document.createElement('h4');
|
|
689
|
+
h4.textContent = d.name;
|
|
690
|
+
const meta = document.createElement('div');
|
|
691
|
+
meta.className = 'diagram-meta';
|
|
692
|
+
meta.textContent = d.type === 'system' ? '⚙ ' + d.meta : '📄 ' + d.meta;
|
|
693
|
+
const preview = document.createElement('div');
|
|
694
|
+
preview.className = 'diagram-preview';
|
|
693
695
|
const pre = document.createElement('pre');
|
|
694
696
|
pre.className = 'mermaid';
|
|
695
|
-
pre.textContent =
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
697
|
+
pre.textContent = d.content;
|
|
698
|
+
preview.appendChild(pre);
|
|
699
|
+
item.appendChild(h4);
|
|
700
|
+
item.appendChild(meta);
|
|
701
|
+
item.appendChild(preview);
|
|
702
|
+
grid.appendChild(item);
|
|
703
|
+
});
|
|
704
|
+
// Render mini previews
|
|
705
|
+
if (typeof mermaid !== 'undefined' && mermaid.run) {
|
|
706
|
+
mermaid.run({ nodes: grid.querySelectorAll('.mermaid') });
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function openDiagramEditor(name) {
|
|
711
|
+
const d = allDiagrams.find(x => x.name === name);
|
|
712
|
+
if (!d) return;
|
|
713
|
+
currentDiagramName = name;
|
|
714
|
+
currentDiagramSaved = d.content;
|
|
715
|
+
document.getElementById('editor-diagram-name').textContent = name;
|
|
716
|
+
document.getElementById('editor-diagram-meta').textContent = d.type === 'system' ? '(system — read-only)' : '';
|
|
717
|
+
document.getElementById('editor-unsaved').style.display = 'none';
|
|
718
|
+
const codeEl = document.getElementById('diagram-code');
|
|
719
|
+
codeEl.value = d.content;
|
|
720
|
+
codeEl.readOnly = d.type === 'system';
|
|
721
|
+
document.getElementById('diagram-list-view').style.display = 'none';
|
|
722
|
+
document.getElementById('diagram-editor-view').classList.add('active');
|
|
723
|
+
renderPreview(d.content);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function closeDiagramEditor() {
|
|
727
|
+
clearTimeout(previewDebounce); previewDebounce = null;
|
|
728
|
+
if (panzoomInstance) { panzoomInstance.dispose(); panzoomInstance = null; }
|
|
729
|
+
document.getElementById('diagram-editor-view').classList.remove('active');
|
|
730
|
+
document.getElementById('diagram-list-view').style.display = '';
|
|
731
|
+
currentDiagramName = null;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function renderPreview(source) {
|
|
735
|
+
const container = document.getElementById('diagram-preview-render');
|
|
736
|
+
container.textContent = '';
|
|
737
|
+
if (panzoomInstance) { panzoomInstance.dispose(); panzoomInstance = null; }
|
|
738
|
+
if (!source || !source.trim()) { container.textContent = 'Empty diagram'; return; }
|
|
739
|
+
const pre = document.createElement('pre');
|
|
740
|
+
pre.className = 'mermaid';
|
|
741
|
+
pre.textContent = source;
|
|
742
|
+
container.appendChild(pre);
|
|
743
|
+
if (typeof mermaid !== 'undefined' && mermaid.run) {
|
|
744
|
+
mermaid.run({ nodes: [pre] }).then(function() {
|
|
745
|
+
const svg = container.querySelector('svg');
|
|
746
|
+
if (svg && typeof panzoom !== 'undefined') {
|
|
747
|
+
svg.style.width = '100%';
|
|
748
|
+
svg.style.height = '100%';
|
|
749
|
+
panzoomInstance = panzoom(svg, { maxZoom: 10, minZoom: 0.1, smoothScroll: false });
|
|
750
|
+
}
|
|
751
|
+
}).catch(function() {
|
|
752
|
+
container.textContent = 'Mermaid syntax error — check your code';
|
|
753
|
+
});
|
|
702
754
|
}
|
|
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
755
|
}
|
|
710
756
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
if (modalPrevFocus) { modalPrevFocus.focus(); modalPrevFocus = null; }
|
|
757
|
+
function resetPanZoom() {
|
|
758
|
+
if (panzoomInstance) {
|
|
759
|
+
panzoomInstance.moveTo(0, 0);
|
|
760
|
+
panzoomInstance.zoomAbs(0, 0, 1);
|
|
761
|
+
}
|
|
717
762
|
}
|
|
718
763
|
|
|
719
|
-
|
|
764
|
+
// Live preview on code change
|
|
765
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
766
|
+
const codeEl = document.getElementById('diagram-code');
|
|
767
|
+
if (codeEl) {
|
|
768
|
+
codeEl.addEventListener('input', function() {
|
|
769
|
+
document.getElementById('editor-unsaved').style.display = codeEl.value !== currentDiagramSaved ? '' : 'none';
|
|
770
|
+
clearTimeout(previewDebounce);
|
|
771
|
+
previewDebounce = setTimeout(function() { renderPreview(codeEl.value); }, 600);
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
async function saveDiagram() {
|
|
777
|
+
if (!currentDiagramName) return;
|
|
778
|
+
const d = allDiagrams.find(x => x.name === currentDiagramName);
|
|
779
|
+
if (d && d.type === 'system') { toast('System diagrams are read-only', false); return; }
|
|
780
|
+
const content = document.getElementById('diagram-code').value;
|
|
781
|
+
try {
|
|
782
|
+
await api('POST', '/api/diagrams/' + encodeURIComponent(currentDiagramName), { content: content });
|
|
783
|
+
currentDiagramSaved = content;
|
|
784
|
+
if (d) d.content = content;
|
|
785
|
+
document.getElementById('editor-unsaved').style.display = 'none';
|
|
786
|
+
// Re-render grid so mini previews reflect saved content
|
|
787
|
+
diagramsLoaded = false;
|
|
788
|
+
toast('Diagram saved', true);
|
|
789
|
+
} catch (e) {
|
|
790
|
+
toast('Save failed: ' + e.message, false);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
720
793
|
|
|
721
|
-
function
|
|
722
|
-
const src =
|
|
723
|
-
if (!src) return;
|
|
724
|
-
const
|
|
725
|
-
const safeName = label.replace(/[^a-zA-Z0-9_-]/g, '_') + '.mmd';
|
|
794
|
+
function downloadCurrentDiagram() {
|
|
795
|
+
const src = document.getElementById('diagram-code').value;
|
|
796
|
+
if (!src || !currentDiagramName) return;
|
|
797
|
+
const safeName = currentDiagramName.replace(/[^a-zA-Z0-9_-]/g, '_') + '.mmd';
|
|
726
798
|
const blob = new Blob([src], { type: 'text/plain' });
|
|
727
799
|
const a = document.createElement('a');
|
|
728
800
|
const url = URL.createObjectURL(blob);
|
|
@@ -1113,19 +1185,8 @@ function addEnvSuggestion(key, val, hint) {
|
|
|
1113
1185
|
addEnvRow(key, val);
|
|
1114
1186
|
}
|
|
1115
1187
|
|
|
1116
|
-
// Pause auto-refresh while
|
|
1188
|
+
// Pause auto-refresh while MCP & Keys tab is active (editing forms)
|
|
1117
1189
|
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
1190
|
|
|
1130
1191
|
async function refresh() {
|
|
1131
1192
|
if (editingLock) return;
|
|
@@ -1195,6 +1256,41 @@ const server = http.createServer(async (req, res) => {
|
|
|
1195
1256
|
return json(res, 200, getDiagramsData());
|
|
1196
1257
|
}
|
|
1197
1258
|
|
|
1259
|
+
// Save diagram content
|
|
1260
|
+
const diagramMatch = parsedUrl.pathname.match(/^\/api\/diagrams\/([a-zA-Z0-9_-]+)$/);
|
|
1261
|
+
if (req.method === 'POST' && diagramMatch) {
|
|
1262
|
+
const name = decodeURIComponent(diagramMatch[1]);
|
|
1263
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(name)) return json(res, 400, { error: 'Invalid diagram name' });
|
|
1264
|
+
const { body, err } = await parseJSONBody(req, res);
|
|
1265
|
+
if (err) return;
|
|
1266
|
+
if (typeof body.content !== 'string') return json(res, 400, { error: 'content must be a string' });
|
|
1267
|
+
if (body.content.length > 500000) return json(res, 400, { error: 'content too large (max 500KB)' });
|
|
1268
|
+
const diagramsDir = path.join(basePath, '.claude', 'pm', 'diagrams');
|
|
1269
|
+
if (!fs.existsSync(diagramsDir)) fs.mkdirSync(diagramsDir, { recursive: true });
|
|
1270
|
+
fs.writeFileSync(path.join(diagramsDir, name + '.mmd'), body.content, 'utf8');
|
|
1271
|
+
// Update metadata timestamp if exists, or create
|
|
1272
|
+
const metaPath = path.join(diagramsDir, name + '.meta.json');
|
|
1273
|
+
let meta = {};
|
|
1274
|
+
try { meta = JSON.parse(fs.readFileSync(metaPath, 'utf8')); } catch {}
|
|
1275
|
+
meta.name = meta.name || name;
|
|
1276
|
+
meta.updated = new Date().toISOString().replace(/\\.\\d{3}Z$/, 'Z');
|
|
1277
|
+
if (!meta.created) meta.created = meta.updated;
|
|
1278
|
+
fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2), 'utf8');
|
|
1279
|
+
return json(res, 200, { ok: true });
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
const diagramDeleteMatch = parsedUrl.pathname.match(/^\/api\/diagrams\/([a-zA-Z0-9_-]+)$/);
|
|
1283
|
+
if (req.method === 'DELETE' && diagramDeleteMatch) {
|
|
1284
|
+
const name = decodeURIComponent(diagramDeleteMatch[1]);
|
|
1285
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(name)) return json(res, 400, { error: 'Invalid diagram name' });
|
|
1286
|
+
const diagramsDir = path.join(basePath, '.claude', 'pm', 'diagrams');
|
|
1287
|
+
const mmdPath = path.join(diagramsDir, name + '.mmd');
|
|
1288
|
+
const metaPath = path.join(diagramsDir, name + '.meta.json');
|
|
1289
|
+
try { fs.unlinkSync(mmdPath); } catch {}
|
|
1290
|
+
try { fs.unlinkSync(metaPath); } catch {}
|
|
1291
|
+
return json(res, 200, { ok: true });
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1198
1294
|
if (req.method === 'GET' && parsedUrl.pathname === '/api/tests') {
|
|
1199
1295
|
return json(res, 200, getTestsData());
|
|
1200
1296
|
}
|
package/package.json
CHANGED