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
- diagrams.push({
256
- name: f.replace(/\.mmd$/, ''),
257
- content: fs.readFileSync(path.join(diagramsDir, f), 'utf8')
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 cards */
369
- .diagram-card { position: relative; }
370
- .diagram-card .diagram-toolbar { display: flex; gap: 6px; position: absolute; top: 12px; right: 12px; z-index: 2; }
371
- .diagram-card .diagram-toolbar button {
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: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 12px;
392
+ padding: 5px 12px; border-radius: 4px; cursor: pointer; font-size: 12px;
374
393
  }
375
- .diagram-card .diagram-toolbar button:hover { background: #30363d; color: #c9d1d9; }
376
- .diagram-card .mermaid-wrap { overflow: auto; max-height: 400px; cursor: pointer; }
377
- .diagram-card .mermaid-wrap:hover { outline: 1px solid #30363d; border-radius: 4px; }
378
- /* Fullscreen modal */
379
- .diagram-modal {
380
- display: none; position: fixed; inset: 0; z-index: 1000;
381
- background: rgba(0,0,0,0.85); justify-content: center; align-items: center;
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-modal.open { display: flex; }
384
- .diagram-modal .modal-inner {
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-modal .modal-header {
389
- display: flex; justify-content: space-between; align-items: center;
390
- padding: 12px 20px; border-bottom: 1px solid #30363d;
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-modal .modal-header h3 { color: #c9d1d9; font-size: 14px; }
393
- .diagram-modal .modal-header .modal-actions { display: flex; gap: 8px; }
394
- .diagram-modal .modal-header button {
395
- background: #21262d; color: #8b949e; border: 1px solid #30363d;
396
- padding: 6px 14px; border-radius: 4px; cursor: pointer; font-size: 12px;
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-modal .modal-header button:hover { background: #30363d; color: #c9d1d9; }
399
- .diagram-modal .modal-body {
400
- flex: 1; overflow: auto; padding: 20px; display: flex;
401
- justify-content: center; align-items: flex-start;
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
- <div id="diagram-epic-card" class="card diagram-card" style="display:none;">
535
- <h3>Epic Flow <span style="color:#8b949e;font-size:11px;font-weight:normal;margin-left:8px;">PRD → Epic → Tasks workflow</span></h3>
536
- <div class="diagram-toolbar">
537
- <button onclick="downloadMermaid('diagram-epic')">↓ .mmd</button>
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 &lt;name&gt;</code></p>
542
566
  </div>
567
+ <div id="diagram-grid" class="diagram-grid"></div>
543
568
  </div>
544
- <div id="project-diagrams"></div>
545
- </div>
546
-
547
- <!-- Diagram fullscreen modal -->
548
- <div class="diagram-modal" id="diagram-modal" role="dialog" aria-modal="true" aria-labelledby="modal-diagram-title" onclick="if(event.target===this)closeDiagramModal()">
549
- <div class="modal-inner">
550
- <div class="modal-header">
551
- <h3 id="modal-diagram-title"></h3>
552
- <div class="modal-actions">
553
- <button onclick="downloadMermaid(currentModalDiagram)">↓ Download .mmd</button>
554
- <button onclick="closeDiagramModal()">✕ Close</button>
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,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'); }
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') renderMermaid();
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
- let diagramsRendered = false;
600
- const diagramSources = {};
601
- const diagramNames = {};
602
- let currentModalDiagram = null;
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 renderMermaid() {
605
- if (diagramsRendered) return;
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
- // Epic Flow only show if there are actual epics/PRDs
610
- const epicCard = document.getElementById('diagram-epic-card');
611
- const epicEl = document.getElementById('diagram-epic');
657
+ // Merge system-generated + project diagrams into one list
658
+ allDiagrams = [];
612
659
  if (data.epicFlowHasData) {
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 = '';
660
+ allDiagrams.push({ name: 'epic-flow', content: data.epicFlow, type: 'system', meta: 'PRD → Epic → Tasks' });
622
661
  }
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);
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
- 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;
680
- });
681
- }
682
- diagramsRendered = true;
669
+ renderDiagramGrid();
670
+ diagramsLoaded = true;
683
671
  });
684
672
  }
685
673
 
686
- function openDiagramModal(diagramId, title) {
687
- currentModalDiagram = diagramId;
688
- document.getElementById('modal-diagram-title').textContent = title || diagramId;
689
- const body = document.getElementById('modal-diagram-body');
690
- body.textContent = '';
691
- const source = diagramSources[diagramId];
692
- if (source) {
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 &lt;name&gt;</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 = source;
696
- body.appendChild(pre);
697
- if (typeof mermaid !== 'undefined' && mermaid.run) {
698
- mermaid.run({ nodes: body.querySelectorAll('.mermaid') });
699
- }
700
- } else {
701
- body.textContent = 'Diagram not available.';
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
- let modalPrevFocus = null;
712
- function closeDiagramModal() {
713
- document.getElementById('diagram-modal').classList.remove('open');
714
- document.removeEventListener('keydown', modalEscHandler);
715
- currentModalDiagram = null;
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
- function modalEscHandler(e) { if (e.key === 'Escape') closeDiagramModal(); }
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 downloadMermaid(diagramId) {
722
- const src = diagramSources[diagramId];
723
- if (!src) return;
724
- const label = diagramNames[diagramId] || diagramId.replace('proj-diagram-', 'diagram-').replace('diagram-', '');
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 user is editing MCP or env forms
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-autopm",
3
- "version": "3.22.1",
3
+ "version": "3.22.2",
4
4
  "description": "Autonomous Project Management Framework for Claude Code - Advanced AI-powered development automation",
5
5
  "main": "bin/autopm.js",
6
6
  "workspaces": [