@yemi33/minions 0.1.2139 → 0.1.2141

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.
@@ -0,0 +1,124 @@
1
+ // confirm-dialog.js — Promise-based custom confirm dialog primitive.
2
+ //
3
+ // Replaces native window.confirm() for actions where the dashboard wants
4
+ // a styled, accessible modal. The HTML scaffold lives in layout.html
5
+ // (#confirm-dialog) and the styles live in styles.css (.confirm-card,
6
+ // .confirm-actions). Both the trigger and the dialog buttons are wired
7
+ // to the same Promise so callers can `await confirmDialog({...})`.
8
+ //
9
+ // Behavior contract (matches WI W-mq5dk1lq):
10
+ // - Escape closes (resolves false)
11
+ // - Backdrop click closes (resolves false)
12
+ // - Enter activates the focused button (default focus is the confirm
13
+ // button, so Enter == confirm in the common case)
14
+ // - Tab / Shift+Tab cycle between Cancel and Confirm (focus trap)
15
+ // - Focus returns to the element that opened the dialog on close
16
+ // - role="dialog", aria-modal="true", aria-labelledby points at the title
17
+ //
18
+ // API:
19
+ // confirmDialog({title, message, confirmLabel, cancelLabel, danger})
20
+ // -> Promise<boolean> (true = confirmed, false = cancelled)
21
+
22
+ (function() {
23
+ let _activeResolve = null;
24
+ let _previousFocus = null;
25
+ let _keyHandler = null;
26
+
27
+ function getEl() { return document.getElementById('confirm-dialog'); }
28
+
29
+ function getFocusable(scope) {
30
+ return Array.from(scope.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'))
31
+ .filter(el => !el.disabled && el.offsetParent !== null);
32
+ }
33
+
34
+ function closeDialog(result) {
35
+ const el = getEl();
36
+ if (!el) return;
37
+ el.classList.remove('open');
38
+ if (_keyHandler) {
39
+ document.removeEventListener('keydown', _keyHandler, true);
40
+ _keyHandler = null;
41
+ }
42
+ const resolver = _activeResolve;
43
+ _activeResolve = null;
44
+ // Restore focus to the trigger element (button/link/etc) on close.
45
+ const prev = _previousFocus;
46
+ _previousFocus = null;
47
+ if (prev && typeof prev.focus === 'function') {
48
+ try { prev.focus(); } catch { /* ignore — element may be gone */ }
49
+ }
50
+ if (resolver) resolver(!!result);
51
+ }
52
+
53
+ function confirmDialog(opts) {
54
+ opts = opts || {};
55
+ const el = getEl();
56
+ if (!el) {
57
+ // Defensive: if the HTML scaffold is missing, fall back to native
58
+ // confirm so the calling action still works (e.g. in a test harness
59
+ // that loads the JS without the layout).
60
+ return Promise.resolve(window.confirm(opts.message || opts.title || 'Confirm?'));
61
+ }
62
+ // If a prior dialog is somehow still open, cancel it before reusing
63
+ // the shared scaffold.
64
+ if (_activeResolve) closeDialog(false);
65
+
66
+ const titleEl = el.querySelector('#confirm-dialog-title');
67
+ const msgEl = el.querySelector('#confirm-dialog-message');
68
+ const confirmBtn = el.querySelector('#confirm-dialog-confirm');
69
+ const cancelBtn = el.querySelector('#confirm-dialog-cancel');
70
+
71
+ titleEl.textContent = opts.title || 'Confirm';
72
+ msgEl.textContent = opts.message || '';
73
+ confirmBtn.textContent = opts.confirmLabel || 'OK';
74
+ cancelBtn.textContent = opts.cancelLabel || 'Cancel';
75
+ // Style the confirm action: red border/text for destructive intent,
76
+ // solid blue for the default reversible/action case. Both reuse the
77
+ // existing .btn primitive in styles.css so the dialog inherits the
78
+ // app's button look.
79
+ confirmBtn.className = 'btn ' + (opts.danger ? 'btn-danger' : 'btn-primary');
80
+ cancelBtn.className = 'btn';
81
+
82
+ _previousFocus = document.activeElement;
83
+
84
+ return new Promise((resolve) => {
85
+ _activeResolve = resolve;
86
+
87
+ el.classList.add('open');
88
+ // Default-focus the confirm button so Enter activates it natively.
89
+ // (Spec: "Enter submits (confirm)".) Defer to next tick so the
90
+ // browser sees the dialog as visible before focus is moved.
91
+ setTimeout(() => { try { confirmBtn.focus(); } catch { /* ignore */ } }, 0);
92
+
93
+ _keyHandler = function(ev) {
94
+ if (!el.classList.contains('open')) return;
95
+ if (ev.key === 'Escape') {
96
+ ev.preventDefault();
97
+ closeDialog(false);
98
+ return;
99
+ }
100
+ if (ev.key === 'Tab') {
101
+ // Focus trap: keep tab cycling between the two buttons.
102
+ const focusable = getFocusable(el);
103
+ if (focusable.length === 0) return;
104
+ const first = focusable[0];
105
+ const last = focusable[focusable.length - 1];
106
+ if (ev.shiftKey && document.activeElement === first) {
107
+ ev.preventDefault();
108
+ last.focus();
109
+ } else if (!ev.shiftKey && document.activeElement === last) {
110
+ ev.preventDefault();
111
+ first.focus();
112
+ }
113
+ }
114
+ // Enter / Space are handled natively by whichever button has focus.
115
+ };
116
+ document.addEventListener('keydown', _keyHandler, true);
117
+ });
118
+ }
119
+
120
+ window.confirmDialog = confirmDialog;
121
+ // Exposed so the inline onclick handlers on the dialog backdrop /
122
+ // Cancel / Confirm buttons can resolve the promise.
123
+ window._confirmDialogResolve = closeDialog;
124
+ })();
@@ -77,10 +77,21 @@ async function submitPinnedNote(e) {
77
77
  }
78
78
 
79
79
  async function removePinnedNote(title) {
80
- if (!confirm('Unpin "' + title + '"?')) return;
80
+ // Capture the trigger before awaiting so confirmDialog can restore focus
81
+ // to the actual Unpin button (window.event is cleared by the time the
82
+ // dialog resolves).
83
+ const btn = (window.event)?.target;
84
+ const ok = await confirmDialog({
85
+ title: 'Unpin context?',
86
+ message: 'This will remove "' + title + '" from your pinned context. You can re-pin it anytime.',
87
+ confirmLabel: 'Unpin',
88
+ cancelLabel: 'Cancel',
89
+ danger: false,
90
+ });
91
+ if (!ok) return;
81
92
  showToast('cmd-toast', 'Note unpinned', true);
82
93
  markDeleted('pin:' + title);
83
- const btn = (window.event)?.target; if (btn) { const card = btn.closest('.pinned-card') || btn.parentElement?.parentElement; if (card) card.remove(); }
94
+ if (btn) { const card = btn.closest('.pinned-card') || btn.parentElement?.parentElement; if (card) card.remove(); }
84
95
  try {
85
96
  const res = await fetch('/api/pinned/remove', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title }) });
86
97
  if (!res.ok) { clearDeleted('pin:' + title); showToast('cmd-toast', 'Unpin failed', false); refresh(); }
@@ -159,6 +159,22 @@
159
159
  <!-- Floating "Ask about selection" button -->
160
160
  <div class="ask-selection-btn" id="ask-selection-btn" onclick="modalAskAboutSelection()">Ask about this</div>
161
161
 
162
+ <!-- Custom confirm dialog — replaces native window.confirm() for actions
163
+ that want the dashboard's styled, accessible modal (W-mq5dk1lq).
164
+ The contents are filled in by confirmDialog() in confirm-dialog.js. -->
165
+ <div class="modal-bg confirm-dialog-bg" id="confirm-dialog" role="dialog" aria-modal="true" aria-labelledby="confirm-dialog-title" onclick="if(event.target===this)_confirmDialogResolve(false)">
166
+ <div class="modal confirm-card">
167
+ <div class="modal-header">
168
+ <h3 id="confirm-dialog-title">Confirm</h3>
169
+ </div>
170
+ <div class="modal-body confirm-message" id="confirm-dialog-message"></div>
171
+ <div class="confirm-actions">
172
+ <button type="button" class="btn" id="confirm-dialog-cancel" onclick="_confirmDialogResolve(false)">Cancel</button>
173
+ <button type="button" class="btn btn-primary" id="confirm-dialog-confirm" onclick="_confirmDialogResolve(true)">OK</button>
174
+ </div>
175
+ </div>
176
+ </div>
177
+
162
178
  <script>/* __JS__ */</script>
163
179
  </body>
164
180
  </html>
@@ -745,6 +745,18 @@
745
745
  .modal-copy.copied { color: var(--green); border-color: var(--green); }
746
746
  .modal-close { background: none; border: none; color: var(--muted); font-size: var(--text-2xl); cursor: pointer; padding: var(--space-2) var(--space-4); }
747
747
  .modal-close:hover { color: var(--text); }
748
+
749
+ /* Custom confirm dialog (W-mq5dk1lq) — promise-based replacement for
750
+ native window.confirm(). Scaffold in layout.html (#confirm-dialog),
751
+ behavior in dashboard/js/confirm-dialog.js. Reuses .modal-bg / .modal
752
+ for backdrop + card chrome; the rules below size the card down to a
753
+ compact "are you sure?" footprint and style the action row. */
754
+ .confirm-dialog-bg { z-index: 500; }
755
+ .modal.confirm-card { width: auto; max-width: 480px; min-width: 320px; }
756
+ .confirm-message { white-space: normal; font-family: 'Segoe UI', system-ui, sans-serif; font-size: var(--text-md); color: var(--text); line-height: 1.5; }
757
+ .confirm-actions { display: flex; justify-content: flex-end; gap: var(--space-4); padding: var(--space-5) var(--space-8) var(--space-7); }
758
+ .confirm-actions .btn { min-width: 80px; }
759
+ .confirm-actions .btn:focus-visible { outline: 2px solid var(--blue); outline-offset: 1px; }
748
760
  .notif-badge { position: absolute; top: -5px; right: -5px; pointer-events: none; }
749
761
  .notif-badge.done { width: 8px; height: 8px; background: var(--red); border-radius: 50%; animation: notifPulse 2s infinite; }
750
762
  .notif-badge.processing { display: flex; gap: 2px; align-items: center; }
@@ -34,7 +34,7 @@ function buildDashboardHtml() {
34
34
  'render-prs', 'render-plans', 'render-inbox', 'render-kb', 'render-skills',
35
35
  'render-other', 'render-managed', 'render-schedules', 'render-watches', 'render-pipelines', 'render-meetings', 'render-pinned',
36
36
  'command-parser', 'command-input', 'command-center', 'command-history',
37
- 'modal', 'modal-qa', 'settings', 'qa', 'fre', 'refresh'
37
+ 'confirm-dialog', 'modal', 'modal-qa', 'settings', 'qa', 'fre', 'refresh'
38
38
  ];
39
39
  let jsHtml = '';
40
40
  for (const f of jsFiles) {
package/dashboard.js CHANGED
@@ -1098,7 +1098,7 @@ function buildDashboardHtml() {
1098
1098
  'render-prs', 'render-plans', 'render-inbox', 'render-kb', 'render-skills',
1099
1099
  'render-other', 'render-managed', 'render-schedules', 'render-watches', 'render-pipelines', 'render-meetings', 'render-pinned',
1100
1100
  'command-parser', 'command-input', 'command-center', 'command-history',
1101
- 'modal', 'modal-qa', 'settings', 'qa', 'fre', 'refresh'
1101
+ 'confirm-dialog', 'modal', 'modal-qa', 'settings', 'qa', 'fre', 'refresh'
1102
1102
  ];
1103
1103
  let jsHtml = '';
1104
1104
  for (const f of jsFiles) {
package/engine/github.js CHANGED
@@ -99,6 +99,20 @@ function getRepoSlug(project) {
99
99
  return `${org}/${repo}`;
100
100
  }
101
101
 
102
+ function getConfiguredGitHubAuthorLogins(config = {}) {
103
+ const accounts = config?.engine?.ghAccounts;
104
+ if (!accounts || typeof accounts !== 'object') return new Set();
105
+ const logins = new Set();
106
+ for (const value of Object.values(accounts)) {
107
+ const values = Array.isArray(value) ? value : [value];
108
+ for (const login of values) {
109
+ const normalized = String(login || '').trim().toLowerCase();
110
+ if (normalized) logins.add(normalized);
111
+ }
112
+ }
113
+ return logins;
114
+ }
115
+
102
116
  // P-f23classifier (F4): delegates to the host-agnostic VERDICT prefix check
103
117
  // in engine/comment-classifier.js. The literal regex used to live here;
104
118
  // extraction allowed F4 (Wave 3) to tighten the prefix in a single place.
@@ -1222,6 +1236,7 @@ async function pollPrHumanComments(config) {
1222
1236
  async function reconcilePrs(config) {
1223
1237
  const projects = getProjects(config).filter(isGitHub);
1224
1238
  const branchPatterns = [/^work\//i, /^feat\//i, /^fix\//i, /^e2e\//i, /^user\/yemishin\//i];
1239
+ const configuredAuthorLogins = getConfiguredGitHubAuthorLogins(config);
1225
1240
  let totalAdded = 0;
1226
1241
 
1227
1242
  for (const project of projects) {
@@ -1322,11 +1337,11 @@ async function reconcilePrs(config) {
1322
1337
  continue;
1323
1338
  }
1324
1339
 
1325
- // E2E branches are always Minions-generated (verify tasks) track even without a work item ID in the branch name
1326
- const isE2eBranch = /^e2e\//i.test(branch);
1327
-
1328
- // Only auto-track PRs linked to a minions work item — skip human-authored PRs
1329
- if (!confirmedItemId && !isE2eBranch) continue;
1340
+ // Auto-import only when local state proves this branch belongs to a
1341
+ // Minions work item and the PR was opened by one of this client's
1342
+ // configured GitHub accounts. Existing/manual records are handled above.
1343
+ const authorLogin = String(ghPr.user?.login || '').trim().toLowerCase();
1344
+ if (!confirmedItemId || !configuredAuthorLogins.has(authorLogin)) continue;
1330
1345
 
1331
1346
  const entry = {
1332
1347
  id: prId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2139",
3
+ "version": "0.1.2141",
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"