@yemi33/minions 0.1.1561 → 0.1.1563

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1563 (2026-04-27)
4
+
5
+ ### Fixes
6
+ - tighten destructive dashboard UX
7
+
8
+ ### Other
9
+ - chore: minions uninstall prints re-install command
10
+
3
11
  ## 0.1.1561 (2026-04-27)
4
12
 
5
13
  ### Other
package/bin/minions.js CHANGED
@@ -713,7 +713,9 @@ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
713
713
  console.log(' Uninstalling npm package...');
714
714
  try { execSync('npm uninstall -g @yemi33/minions', { stdio: 'inherit', timeout: 60000 }); } catch {}
715
715
 
716
- console.log('\n Minions uninstalled. Your project repos were not touched.\n');
716
+ console.log('\n Minions uninstalled. Your project repos were not touched.');
717
+ console.log('\n To re-install later, run:');
718
+ console.log(' npm install -g @yemi33/minions && minions init\n');
717
719
  } else if (cmd === 'doctor') {
718
720
  ensureInstalled();
719
721
  const { doctor } = require(path.join(MINIONS_HOME, 'engine', 'preflight'));
@@ -158,17 +158,17 @@ function modalCancelEdit() {
158
158
 
159
159
  async function deleteInboxItem(name) {
160
160
  if (!confirm('Delete "' + name + '" from inbox?')) return;
161
+ showToast('cmd-toast', 'Inbox item deleted', true);
161
162
  markDeleted('inbox:' + name);
162
163
  const card = document.querySelector('.inbox-item[data-file="notes/inbox/' + CSS.escape(name) + '"]');
163
164
  if (card) card.remove();
164
- showToast('cmd-toast', 'Inbox item deleted', true);
165
165
  try {
166
166
  const res = await fetch('/api/inbox/delete', {
167
167
  method: 'POST', headers: { 'Content-Type': 'application/json' },
168
168
  body: JSON.stringify({ name })
169
169
  });
170
- if (!res.ok) { const d = await res.json().catch(() => ({})); showToast('cmd-toast', 'Delete failed: ' + (d.error || 'unknown'), false); refresh(); }
171
- } catch (e) { showToast('cmd-toast', 'Delete error: ' + e.message, false); refresh(); }
170
+ if (!res.ok) { const d = await res.json().catch(() => ({})); clearDeleted('inbox:' + name); showToast('cmd-toast', 'Delete failed: ' + (d.error || 'unknown'), false); refresh(); }
171
+ } catch (e) { clearDeleted('inbox:' + name); showToast('cmd-toast', 'Delete error: ' + e.message, false); refresh(); }
172
172
  }
173
173
 
174
174
  async function openInboxInExplorer(name) {
@@ -223,11 +223,11 @@ async function submitQuickNote() {
223
223
  }
224
224
 
225
225
  async function doPromoteToKB(name, category) {
226
+ showToast('cmd-toast', 'Promoted to Knowledge Base', true);
226
227
  try { closeModal(); } catch { /* expected */ }
227
228
  markDeleted('inbox:' + name);
228
229
  const card = document.querySelector('.inbox-item[data-file="notes/inbox/' + CSS.escape(name) + '"]');
229
230
  if (card) card.remove();
230
- showToast('cmd-toast', 'Promoted to Knowledge Base', true);
231
231
  try {
232
232
  const res = await fetch('/api/inbox/promote-kb', {
233
233
  method: 'POST', headers: { 'Content-Type': 'application/json' },
@@ -235,8 +235,8 @@ async function doPromoteToKB(name, category) {
235
235
  });
236
236
  const data = await res.json();
237
237
  if (res.ok) { refreshKnowledgeBase(); }
238
- else { showToast('cmd-toast', 'Promote failed: ' + (data.error || 'unknown'), false); refresh(); }
239
- } catch (e) { showToast('cmd-toast', 'Promote error: ' + e.message, false); refresh(); }
238
+ else { clearDeleted('inbox:' + name); showToast('cmd-toast', 'Promote failed: ' + (data.error || 'unknown'), false); refresh(); }
239
+ } catch (e) { clearDeleted('inbox:' + name); showToast('cmd-toast', 'Promote error: ' + e.message, false); refresh(); }
240
240
  }
241
241
 
242
242
  window.MinionsInbox = { renderInbox, promoteToKB, renderNotes, openNotesModal, modalToggleEdit, modalSaveEdit, modalCancelEdit, deleteInboxItem, openInboxInExplorer, openQuickNoteModal, submitQuickNote, doPromoteToKB };
@@ -337,17 +337,17 @@ async function _endMeeting(id, btn) {
337
337
  }
338
338
 
339
339
  async function _archiveMeeting(id) {
340
+ showToast('cmd-toast', 'Meeting archived', true);
340
341
  markDeleted('mtg:' + id);
341
342
  try { closeModal(); } catch { /* may not be open */ }
342
343
  document.querySelectorAll('[onclick*="openMeetingDetail(\'' + id + '\')"]').forEach(function(el) { el.remove(); });
343
- showToast('cmd-toast', 'Meeting archived', true);
344
344
  try {
345
345
  const res = await fetch('/api/meetings/archive', {
346
346
  method: 'POST', headers: { 'Content-Type': 'application/json' },
347
347
  body: JSON.stringify({ id })
348
348
  });
349
- if (!res.ok) { _deletedIds.delete('mtg:' + id); const d = await res.json().catch(() => ({})); showToast('cmd-toast', 'Failed: ' + (d.error || 'unknown'), false); refresh(); }
350
- } catch (e) { _deletedIds.delete('mtg:' + id); showToast('cmd-toast', 'Error: ' + e.message, false); refresh(); }
349
+ if (!res.ok) { clearDeleted('mtg:' + id); const d = await res.json().catch(() => ({})); showToast('cmd-toast', 'Failed: ' + (d.error || 'unknown'), false); refresh(); }
350
+ } catch (e) { clearDeleted('mtg:' + id); showToast('cmd-toast', 'Error: ' + e.message, false); refresh(); }
351
351
  }
352
352
 
353
353
  async function _unarchiveMeeting(id) {
@@ -480,17 +480,17 @@ async function _createPlanFromMeeting(id, btn) {
480
480
 
481
481
  async function _deleteMeeting(id) {
482
482
  if (!confirm('Delete this meeting? This cannot be undone.')) return;
483
+ showToast('cmd-toast', 'Meeting deleted', true);
483
484
  markDeleted('mtg:' + id);
484
485
  try { closeModal(); } catch { /* may not be open */ }
485
486
  document.querySelectorAll('[onclick*="openMeetingDetail(\'' + id + '\')"]').forEach(function(el) { el.remove(); });
486
- showToast('cmd-toast', 'Meeting deleted', true);
487
487
  try {
488
488
  const res = await fetch('/api/meetings/delete', {
489
489
  method: 'POST', headers: { 'Content-Type': 'application/json' },
490
490
  body: JSON.stringify({ id })
491
491
  });
492
- if (!res.ok) { const d = await res.json().catch(() => ({})); showToast('cmd-toast', 'Failed: ' + (d.error || 'unknown'), false); refresh(); }
493
- } catch (e) { showToast('cmd-toast', 'Error: ' + e.message, false); refresh(); }
492
+ if (!res.ok) { clearDeleted('mtg:' + id); const d = await res.json().catch(() => ({})); showToast('cmd-toast', 'Failed: ' + (d.error || 'unknown'), false); refresh(); }
493
+ } catch (e) { clearDeleted('mtg:' + id); showToast('cmd-toast', 'Error: ' + e.message, false); refresh(); }
494
494
  }
495
495
 
496
496
  window.MinionsMeetings = { renderMeetings, openMeetingDetail, openCreateMeetingModal };
@@ -23,12 +23,11 @@ function renderProjects(projects) {
23
23
 
24
24
  async function projectChipRemove(name) {
25
25
  if (!confirm('Remove project "' + name + '"? Pending work cancels, active agents are killed, data dir is archived to projects/.archived/.')) return;
26
+ showToast('cmd-toast', 'Removing project "' + name + '"...', true);
26
27
  markDeleted('project:' + name);
27
- // Re-render immediately from cached state so the chip disappears without waiting on the API
28
28
  if (window._lastStatus && Array.isArray(window._lastStatus.projects)) {
29
29
  renderProjects(window._lastStatus.projects);
30
30
  }
31
- showToast('cmd-toast', 'Removing project "' + name + '"...', true);
32
31
  try {
33
32
  const res = await fetch('/api/projects/remove', {
34
33
  method: 'POST', headers: { 'Content-Type': 'application/json' },
@@ -37,6 +36,8 @@ async function projectChipRemove(name) {
37
36
  const d = await res.json().catch(() => ({}));
38
37
  if (!res.ok || !d.ok) {
39
38
  // Revert: refresh restores the chip from server state
39
+ clearDeleted('project:' + name);
40
+ if (window._lastStatus && Array.isArray(window._lastStatus.projects)) renderProjects(window._lastStatus.projects);
40
41
  showToast('cmd-toast', 'Remove failed: ' + (d.error || 'unknown'), false);
41
42
  if (typeof refresh === 'function') refresh();
42
43
  return;
@@ -50,6 +51,8 @@ async function projectChipRemove(name) {
50
51
  showToast('cmd-toast', parts.join(' — '), true);
51
52
  if (typeof refresh === 'function') refresh();
52
53
  } catch (e) {
54
+ clearDeleted('project:' + name);
55
+ if (window._lastStatus && Array.isArray(window._lastStatus.projects)) renderProjects(window._lastStatus.projects);
53
56
  showToast('cmd-toast', 'Error: ' + e.message, false);
54
57
  if (typeof refresh === 'function') refresh();
55
58
  }
@@ -77,13 +77,13 @@ async function submitPinnedNote(e) {
77
77
 
78
78
  async function removePinnedNote(title) {
79
79
  if (!confirm('Unpin "' + title + '"?')) return;
80
+ showToast('cmd-toast', 'Note unpinned', true);
80
81
  markDeleted('pin:' + title);
81
82
  const btn = (window.event)?.target; if (btn) { const card = btn.closest('.pinned-card') || btn.parentElement?.parentElement; if (card) card.remove(); }
82
- showToast('cmd-toast', 'Note unpinned', true);
83
83
  try {
84
84
  const res = await fetch('/api/pinned/remove', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title }) });
85
- if (!res.ok) { showToast('cmd-toast', 'Unpin failed', false); refresh(); }
86
- } catch (e) { showToast('cmd-toast', 'Error: ' + e.message, false); refresh(); }
85
+ if (!res.ok) { clearDeleted('pin:' + title); showToast('cmd-toast', 'Unpin failed', false); refresh(); }
86
+ } catch (e) { clearDeleted('pin:' + title); showToast('cmd-toast', 'Error: ' + e.message, false); refresh(); }
87
87
  }
88
88
 
89
89
  function openPinnedView(idx) {
@@ -248,7 +248,8 @@ function _buildNodeChain(stages, run, options) {
248
248
  }
249
249
 
250
250
  function renderPipelines(pipelines) {
251
- _pipelinesData = pipelines || [];
251
+ pipelines = (pipelines || []).filter(function(p) { return !isDeleted('pipeline:' + p.id); });
252
+ _pipelinesData = pipelines;
252
253
  const el = document.getElementById('pipelines-content');
253
254
  const countEl = document.getElementById('pipelines-count');
254
255
  if (!el) return;
@@ -515,14 +516,15 @@ async function _continuePipeline(id, stageId, btn) {
515
516
 
516
517
  async function _deletePipelineConfirm(id) {
517
518
  if (!confirm('Delete pipeline "' + id + '"?')) return;
519
+ showToast('cmd-toast', 'Pipeline deleted', true);
518
520
  markDeleted('pipeline:' + id);
519
521
  try { closeModal(); } catch {}
520
- showToast('cmd-toast', 'Pipeline deleted', true);
522
+ renderPipelines(_pipelinesData);
521
523
  try {
522
524
  var res = await fetch('/api/pipelines/delete', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: id }) });
523
- if (!res.ok) { showToast('cmd-toast', 'Delete failed', false); }
525
+ if (!res.ok) { clearDeleted('pipeline:' + id); showToast('cmd-toast', 'Delete failed', false); }
524
526
  refresh();
525
- } catch (e) { showToast('cmd-toast', 'Error: ' + e.message, false); refresh(); }
527
+ } catch (e) { clearDeleted('pipeline:' + id); showToast('cmd-toast', 'Error: ' + e.message, false); refresh(); }
526
528
  }
527
529
 
528
530
  function openCreatePipelineModal() {
@@ -575,9 +575,11 @@ async function planApprove(file, btn) {
575
575
  async function planDelete(file) {
576
576
  _stopPlanPoll();
577
577
  if (!confirm('Delete plan "' + file + '"? This cannot be undone.')) return;
578
- markDeleted('plan:' + file);
579
- closeModal();
580
578
  showToast('cmd-toast', 'Plan deleted', true);
579
+ markDeleted('plan:' + file);
580
+ try { closeModal(); } catch { /* may not be open */ }
581
+ if (window._lastPlans) renderPlans(window._lastPlans);
582
+ if (typeof rerenderPrdFromCache === 'function') rerenderPrdFromCache();
581
583
  try {
582
584
  const res = await fetch('/api/plans/delete', {
583
585
  method: 'POST', headers: { 'Content-Type': 'application/json' },
@@ -587,10 +589,11 @@ async function planDelete(file) {
587
589
  refresh();
588
590
  } else {
589
591
  const d = await res.json().catch(() => ({}));
590
- alert('Delete failed: ' + (d.error || 'unknown'));
592
+ clearDeleted('plan:' + file);
593
+ showToast('cmd-toast', 'Delete failed: ' + (d.error || 'unknown'), false);
591
594
  refresh(); // revert optimistic
592
595
  }
593
- } catch (e) { alert('Error: ' + e.message); refresh(); }
596
+ } catch (e) { clearDeleted('plan:' + file); showToast('cmd-toast', 'Error: ' + e.message, false); refresh(); }
594
597
  }
595
598
 
596
599
  async function planArchive(file, btn) {
@@ -600,6 +603,7 @@ async function planArchive(file, btn) {
600
603
  : 'Archive this plan?';
601
604
  if (!confirm(confirmMsg)) return;
602
605
  _stopPlanPoll();
606
+ showToast('cmd-toast', isPrd ? 'Archiving PRD and linked source plan...' : 'Archiving plan...', true);
603
607
  markDeleted('plan:' + file);
604
608
  if (isPrd) {
605
609
  var prdRecord = (window._lastPlans || []).find(function(p) { return p.file === file && p.sourcePlan; });
@@ -608,7 +612,6 @@ async function planArchive(file, btn) {
608
612
  if (window._lastPlans) renderPlans(window._lastPlans);
609
613
  if (typeof rerenderPrdFromCache === 'function') rerenderPrdFromCache();
610
614
  try { closeModal(); } catch { /* may not be open */ }
611
- showToast('cmd-toast', isPrd ? 'Archiving PRD and linked source plan...' : 'Archiving plan...', true);
612
615
  try {
613
616
  const res = await fetch('/api/plans/archive', {
614
617
  method: 'POST', headers: { 'Content-Type': 'application/json' },
@@ -626,10 +629,12 @@ async function planArchive(file, btn) {
626
629
  }
627
630
  refresh();
628
631
  } else {
632
+ clearDeleted('plan:' + file);
633
+ if (prdRecord) clearDeleted('plan:' + prdRecord.sourcePlan);
629
634
  showToast('cmd-toast', 'Archive failed: ' + (d.error || 'unknown'), false);
630
635
  refresh();
631
636
  }
632
- } catch (e) { showToast('cmd-toast', 'Error: ' + e.message, false); refresh(); }
637
+ } catch (e) { clearDeleted('plan:' + file); if (prdRecord) clearDeleted('plan:' + prdRecord.sourcePlan); showToast('cmd-toast', 'Error: ' + e.message, false); refresh(); }
633
638
  }
634
639
 
635
640
  async function planPause(file, btn) {
@@ -1,5 +1,9 @@
1
1
  // render-prd.js — PRD-related rendering functions extracted from dashboard.html
2
2
 
3
+ function _prdItemDeleteKey(source, itemId) {
4
+ return 'prd-item:' + (source || '') + ':' + (itemId || '');
5
+ }
6
+
3
7
  function renderPrd(prd, prog) {
4
8
  const section = document.getElementById('prd-content');
5
9
  const badge = document.getElementById('prd-badge');
@@ -19,7 +23,7 @@ function renderPrd(prd, prog) {
19
23
  // Show per-PRD status summary in header when multiple PRDs exist
20
24
  const existing = (prd.existing || []).filter(function(p) { return !isDeleted('plan:' + (p.file || '')); });
21
25
  const allWi = window._lastWorkItems || [];
22
- const prdItems = (prog?.items || []).filter(i => !i._archived && !isDeleted('plan:' + (i.source || '')));
26
+ const prdItems = (prog?.items || []).filter(i => !i._archived && !isDeleted('plan:' + (i.source || '')) && !isDeleted(_prdItemDeleteKey(i.source, i.id)));
23
27
 
24
28
  if (existing.length === 0 && prdItems.length === 0) {
25
29
  section.innerHTML = '<p class="prd-pending" style="margin-bottom:0">No PRD found.</p>';
@@ -84,7 +88,7 @@ function renderPrdProgress(prog) {
84
88
  const el = document.getElementById('prd-progress-content');
85
89
  const countEl = document.getElementById('prd-progress-count');
86
90
  if (!prog) { el.innerHTML = ''; countEl.textContent = '—'; return; }
87
- const visibleItems = (prog.items || []).filter(i => !isDeleted('plan:' + (i.source || '')));
91
+ const visibleItems = (prog.items || []).filter(i => !isDeleted('plan:' + (i.source || '')) && !isDeleted(_prdItemDeleteKey(i.source, i.id)));
88
92
 
89
93
  // Compute overall progress from active (non-archived) items
90
94
  const activeItems = visibleItems.filter(i => !i._archived);
@@ -748,16 +752,18 @@ async function prdItemSave(source, itemId) {
748
752
 
749
753
  async function prdItemRemove(source, itemId) {
750
754
  if (!confirm('Remove item ' + itemId + '? This also cancels any pending work item.')) return;
751
- closeModal();
752
755
  showToast('cmd-toast', 'Item removed', true);
756
+ markDeleted(_prdItemDeleteKey(source, itemId));
757
+ closeModal();
758
+ if (typeof rerenderPrdFromCache === 'function') rerenderPrdFromCache();
753
759
  try {
754
760
  const res = await fetch('/api/prd-items/remove', {
755
761
  method: 'POST', headers: { 'Content-Type': 'application/json' },
756
762
  body: JSON.stringify({ source, itemId })
757
763
  });
758
764
  if (res.ok) { refresh(); }
759
- else { const d = await res.json().catch(() => ({})); alert('Remove failed: ' + (d.error || 'unknown')); refresh(); }
760
- } catch (e) { alert('Error: ' + e.message); refresh(); }
765
+ else { const d = await res.json().catch(() => ({})); clearDeleted(_prdItemDeleteKey(source, itemId)); showToast('cmd-toast', 'Remove failed: ' + (d.error || 'unknown'), false); refresh(); }
766
+ } catch (e) { clearDeleted(_prdItemDeleteKey(source, itemId)); showToast('cmd-toast', 'Error: ' + e.message, false); refresh(); }
761
767
  }
762
768
 
763
769
  async function prdItemRequeue(workItemId, source, prdFile) {
@@ -154,18 +154,17 @@ async function _submitLinkPr() {
154
154
 
155
155
  async function unlinkPr(id) {
156
156
  if (!confirm('Remove ' + id + ' from tracking?')) return;
157
- // Optimistic: remove row immediately
157
+ showToast('cmd-toast', id + ' removed', true);
158
158
  markDeleted('pr:' + id);
159
159
  const row = document.querySelector('[data-pr-id="' + id + '"]')?.closest('tr');
160
160
  if (row) row.remove();
161
- showToast('cmd-toast', id + ' removed', true);
162
161
  try {
163
162
  const res = await fetch('/api/pull-requests/delete', {
164
163
  method: 'POST', headers: { 'Content-Type': 'application/json' },
165
164
  body: JSON.stringify({ id })
166
165
  });
167
- if (!res.ok) { _deletedIds.delete('pr:' + id); const d = await res.json().catch(() => ({})); alert('Failed: ' + (d.error || 'unknown')); refresh(); return; }
168
- } catch (e) { _deletedIds.delete('pr:' + id); alert('Error: ' + e.message); refresh(); }
166
+ if (!res.ok) { clearDeleted('pr:' + id); const d = await res.json().catch(() => ({})); showToast('cmd-toast', 'Failed: ' + (d.error || 'unknown'), false); refresh(); return; }
167
+ } catch (e) { clearDeleted('pr:' + id); showToast('cmd-toast', 'Error: ' + e.message, false); refresh(); }
169
168
  }
170
169
 
171
170
  window.MinionsPrs = { prRow, prTableHtml, renderPrs, prPrev, prNext, openAllPrs, openModal, openAddPrModal, unlinkPr };
@@ -540,16 +540,16 @@ async function toggleScheduleEnabled(id, enabled) {
540
540
 
541
541
  async function deleteSchedule(id) {
542
542
  if (!confirm('Delete scheduled task "' + id + '"?')) return;
543
+ showToast('cmd-toast', 'Schedule deleted', true);
543
544
  markDeleted('sched:' + id);
544
545
  document.querySelectorAll('tr').forEach(function(r) { if (r.textContent.includes(id)) r.remove(); });
545
- showToast('cmd-toast', 'Schedule deleted', true);
546
546
  try {
547
547
  const res = await fetch('/api/schedules/delete', {
548
548
  method: 'POST', headers: { 'Content-Type': 'application/json' },
549
549
  body: JSON.stringify({ id })
550
550
  });
551
- if (!res.ok) { const d = await res.json().catch(() => ({})); _showScheduleError('Delete failed: ' + (d.error || 'unknown')); refresh(); }
552
- } catch (e) { _showScheduleError('Delete error: ' + e.message); refresh(); }
551
+ if (!res.ok) { const d = await res.json().catch(() => ({})); clearDeleted('sched:' + id); _showScheduleError('Delete failed: ' + (d.error || 'unknown')); refresh(); }
552
+ } catch (e) { clearDeleted('sched:' + id); _showScheduleError('Delete error: ' + e.message); refresh(); }
553
553
  }
554
554
 
555
555
  // Expose _generateScheduleId globally for the inline oninput handler
@@ -193,17 +193,26 @@ function toggleWatchPause(id, pause) {
193
193
 
194
194
  function deleteWatch(id) {
195
195
  if (!confirm('Delete this watch?')) return;
196
- markDeleted('watch:' + id);
197
196
  showToast('cmd-toast', 'Deleting watch...', true);
197
+ markDeleted('watch:' + id);
198
+ renderWatches(window._lastWatches || []);
198
199
  fetch('/api/watches/delete', {
199
200
  method: 'POST',
200
201
  headers: { 'Content-Type': 'application/json' },
201
202
  body: JSON.stringify({ id: id })
202
- }).then(function(res) { return res.json(); }).then(function(data) {
203
- if (data.error) showToast('cmd-toast', 'Error: ' + data.error, false);
204
- else renderWatches(window._lastWatches || []);
203
+ }).then(async function(res) {
204
+ const data = await res.json().catch(() => ({}));
205
+ if (!res.ok || data.error) {
206
+ clearDeleted('watch:' + id);
207
+ showToast('cmd-toast', 'Delete failed: ' + (data.error || 'unknown'), false);
208
+ if (typeof refresh === 'function') refresh();
209
+ return;
210
+ }
211
+ renderWatches(window._lastWatches || []);
205
212
  }).catch(function(err) {
206
- showToast('cmd-toast', 'Error: ' + err.message, false);
213
+ clearDeleted('watch:' + id);
214
+ showToast('cmd-toast', 'Delete error: ' + err.message, false);
215
+ if (typeof refresh === 'function') refresh();
207
216
  });
208
217
  }
209
218
 
@@ -197,46 +197,46 @@ async function submitWorkItemEdit(id, source, e) {
197
197
 
198
198
  async function deleteWorkItem(id, source) {
199
199
  if (!confirm('Delete work item ' + id + '? This will kill any running agent and remove all dispatch history.')) return;
200
+ showToast('cmd-toast', 'Work item deleted', true);
200
201
  markDeleted('wi:' + id);
201
202
  var wiTable = document.getElementById('work-items-content');
202
203
  (wiTable || document).querySelectorAll('tr').forEach(function(r) { if (r.textContent.includes(id)) r.remove(); });
203
- showToast('cmd-toast', 'Work item deleted', true);
204
204
  try {
205
205
  const res = await fetch('/api/work-items/delete', {
206
206
  method: 'POST', headers: { 'Content-Type': 'application/json' },
207
207
  body: JSON.stringify({ id, source: source || undefined })
208
208
  });
209
- if (!res.ok) { const d = await res.json().catch(() => ({})); showToast('cmd-toast', 'Delete failed: ' + (d.error || 'unknown'), false); refresh(); }
210
- } catch (e) { showToast('cmd-toast', 'Delete error: ' + e.message, false); refresh(); }
209
+ if (!res.ok) { const d = await res.json().catch(() => ({})); clearDeleted('wi:' + id); showToast('cmd-toast', 'Delete failed: ' + (d.error || 'unknown'), false); refresh(); }
210
+ } catch (e) { clearDeleted('wi:' + id); showToast('cmd-toast', 'Delete error: ' + e.message, false); refresh(); }
211
211
  }
212
212
 
213
213
  async function cancelWorkItem(id, source) {
214
214
  if (!confirm('Cancel work item ' + id + '? This will kill any running agent and mark it cancelled.')) return;
215
+ showToast('cmd-toast', 'Work item cancelled', true);
215
216
  markDeleted('wi:' + id);
216
217
  var wiTable = document.getElementById('work-items-content');
217
218
  (wiTable || document).querySelectorAll('tr').forEach(function(r) { if (r.textContent.includes(id)) r.remove(); });
218
- showToast('cmd-toast', 'Work item cancelled', true);
219
219
  try {
220
220
  const res = await fetch('/api/work-items/cancel', {
221
221
  method: 'POST', headers: { 'Content-Type': 'application/json' },
222
222
  body: JSON.stringify({ id, source: source || undefined })
223
223
  });
224
- if (!res.ok) { const d = await res.json().catch(() => ({})); showToast('cmd-toast', 'Cancel failed: ' + (d.error || 'unknown'), false); refresh(); }
225
- } catch (e) { showToast('cmd-toast', 'Cancel error: ' + e.message, false); refresh(); }
224
+ if (!res.ok) { const d = await res.json().catch(() => ({})); clearDeleted('wi:' + id); showToast('cmd-toast', 'Cancel failed: ' + (d.error || 'unknown'), false); refresh(); }
225
+ } catch (e) { clearDeleted('wi:' + id); showToast('cmd-toast', 'Cancel error: ' + e.message, false); refresh(); }
226
226
  }
227
227
 
228
228
  async function archiveWorkItem(id, source) {
229
+ showToast('cmd-toast', 'Archived ' + id, true);
229
230
  markDeleted('wi:' + id);
230
231
  var wiTable = document.getElementById('work-items-content');
231
232
  (wiTable || document).querySelectorAll('tr').forEach(function(r) { if (r.textContent.includes(id)) r.remove(); });
232
- showToast('cmd-toast', 'Archived ' + id, true);
233
233
  try {
234
234
  const res = await fetch('/api/work-items/archive', {
235
235
  method: 'POST', headers: { 'Content-Type': 'application/json' },
236
236
  body: JSON.stringify({ id, source: source || undefined })
237
237
  });
238
- if (!res.ok) { const d = await res.json().catch(() => ({})); alert('Archive failed: ' + (d.error || 'unknown')); refresh(); return; }
239
- } catch (e) { alert('Archive error: ' + e.message); refresh(); }
238
+ if (!res.ok) { const d = await res.json().catch(() => ({})); clearDeleted('wi:' + id); showToast('cmd-toast', 'Archive failed: ' + (d.error || 'unknown'), false); refresh(); return; }
239
+ } catch (e) { clearDeleted('wi:' + id); showToast('cmd-toast', 'Archive error: ' + e.message, false); refresh(); }
240
240
  }
241
241
 
242
242
  let wiArchiveVisible = false;
@@ -86,7 +86,7 @@ async function openSettings() {
86
86
  '<h3 style="font-size:13px;color:var(--blue);margin-bottom:8px">Projects</h3>' +
87
87
  '<div style="display:flex;flex-direction:column;gap:12px;margin-bottom:16px">' +
88
88
  (data.projects || []).map(function(p) {
89
- return '<div style="border:1px solid var(--border);border-radius:6px;padding:10px 12px">' +
89
+ return '<div data-settings-project="' + escHtml(p.name) + '" style="border:1px solid var(--border);border-radius:6px;padding:10px 12px">' +
90
90
  '<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px">' +
91
91
  '<div style="font-size:12px;font-weight:600">' + escHtml(p.name) + '</div>' +
92
92
  '<button onclick="MinionsSettings.removeProject(\'' + escHtml(p.name) + '\')" style="font-size:9px;padding:2px 8px;background:transparent;color:var(--red);border:1px solid var(--red);border-radius:3px;cursor:pointer">Remove</button>' +
@@ -409,7 +409,17 @@ async function resetSettingsToDefaults() {
409
409
 
410
410
  async function removeProject(name) {
411
411
  if (!confirm('Remove project "' + name + '"? Pending work cancels, active agents are killed, data dir is archived to projects/.archived/.')) return;
412
+ const prevProjects = (_settingsData && Array.isArray(_settingsData.projects)) ? _settingsData.projects.slice() : null;
412
413
  showToast('cmd-toast', 'Removing project "' + name + '"...', true);
414
+ markDeleted('project:' + name);
415
+ if (_settingsData && Array.isArray(_settingsData.projects)) {
416
+ _settingsData.projects = _settingsData.projects.filter(function(p) { return p.name !== name; });
417
+ }
418
+ const card = document.querySelector('[data-settings-project="' + CSS.escape(name) + '"]');
419
+ if (card) card.remove();
420
+ if (window._lastStatus && Array.isArray(window._lastStatus.projects) && typeof renderProjects === 'function') {
421
+ renderProjects(window._lastStatus.projects);
422
+ }
413
423
  try {
414
424
  const res = await fetch('/api/projects/remove', {
415
425
  method: 'POST', headers: { 'Content-Type': 'application/json' },
@@ -417,7 +427,13 @@ async function removeProject(name) {
417
427
  });
418
428
  const d = await res.json().catch(() => ({}));
419
429
  if (!res.ok || !d.ok) {
430
+ if (prevProjects) _settingsData.projects = prevProjects;
431
+ clearDeleted('project:' + name);
432
+ if (window._lastStatus && Array.isArray(window._lastStatus.projects) && typeof renderProjects === 'function') {
433
+ renderProjects(window._lastStatus.projects);
434
+ }
420
435
  showToast('cmd-toast', 'Remove failed: ' + (d.error || 'unknown'), false);
436
+ openSettings();
421
437
  return;
422
438
  }
423
439
  var parts = ['Removed "' + name + '"'];
@@ -430,7 +446,13 @@ async function removeProject(name) {
430
446
  showToast('cmd-toast', parts.join(' — '), true);
431
447
  setTimeout(() => openSettings(), 600);
432
448
  } catch (e) {
449
+ if (prevProjects) _settingsData.projects = prevProjects;
450
+ clearDeleted('project:' + name);
451
+ if (window._lastStatus && Array.isArray(window._lastStatus.projects) && typeof renderProjects === 'function') {
452
+ renderProjects(window._lastStatus.projects);
453
+ }
433
454
  showToast('cmd-toast', 'Error: ' + e.message, false);
455
+ openSettings();
434
456
  }
435
457
  }
436
458
 
@@ -3,9 +3,10 @@
3
3
  // Signal the engine to tick immediately (pick up new work without waiting 60s)
4
4
  function wakeEngine() { fetch('/api/engine/wakeup', { method: 'POST' }).catch(() => {}); }
5
5
 
6
- // Optimistic delete suppression — prevent auto-refresh from re-showing deleted items
7
- const _deletedIds = new Map(); // key → expiry timestamp
8
- function markDeleted(key) { _deletedIds.set(key, Date.now() + 10000); } // suppress for 10s
6
+ // Optimistic delete suppression
7
+ const _deletedIds = new Map();
8
+ function markDeleted(key) { _deletedIds.set(key, Date.now() + 10000); }
9
+ function clearDeleted(key) { _deletedIds.delete(key); }
9
10
  function isDeleted(key) { const exp = _deletedIds.get(key); if (!exp) return false; if (Date.now() > exp) { _deletedIds.delete(key); return false; } return true; }
10
11
 
11
12
  // Pin-to-top — persisted server-side so CC and agents can also pin items
@@ -507,4 +508,4 @@ async function submitBugReport() {
507
508
  }
508
509
  }
509
510
 
510
- window.MinionsUtils = { wakeEngine, escapeHtml, escHtml, renderMd, normalizePlanFile, timeAgo, statusColor, shouldIgnoreSelectionClick, llmCopyBtn, copyLlmText, openBugReport, submitBugReport };
511
+ window.MinionsUtils = { wakeEngine, markDeleted, clearDeleted, escapeHtml, escHtml, renderMd, normalizePlanFile, timeAgo, statusColor, shouldIgnoreSelectionClick, llmCopyBtn, copyLlmText, openBugReport, submitBugReport };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1561",
3
+ "version": "0.1.1563",
4
4
  "description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
5
5
  "bin": {
6
6
  "minions": "bin/minions.js"