@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.
- package/dashboard/js/confirm-dialog.js +124 -0
- package/dashboard/js/render-pinned.js +13 -2
- package/dashboard/layout.html +16 -0
- package/dashboard/styles.css +12 -0
- package/dashboard-build.js +1 -1
- package/dashboard.js +1 -1
- package/engine/github.js +20 -5
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
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(); }
|
package/dashboard/layout.html
CHANGED
|
@@ -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>
|
package/dashboard/styles.css
CHANGED
|
@@ -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; }
|
package/dashboard-build.js
CHANGED
|
@@ -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
|
-
//
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
if (!confirmedItemId
|
|
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.
|
|
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"
|