@yemi33/minions 0.1.2195 → 0.1.2196
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/modal.js +35 -1
- package/dashboard/js/refresh.js +1 -1
- package/dashboard/js/render-agents.js +18 -0
- package/dashboard/js/render-kb.js +27 -2
- package/dashboard/js/render-meetings.js +36 -3
- package/dashboard/js/render-pipelines.js +15 -10
- package/dashboard/js/render-plans.js +73 -3
- package/dashboard/js/render-prd.js +29 -5
- package/dashboard/js/render-prs.js +174 -3
- package/dashboard/js/render-schedules.js +12 -0
- package/dashboard/js/render-utils.js +151 -1
- package/dashboard/js/render-watches.js +61 -7
- package/dashboard/js/render-work-items.js +161 -20
- package/dashboard/js/state.js +110 -1
- package/dashboard/js/utils.js +246 -14
- package/dashboard/layout.html +2 -0
- package/dashboard/slim/body.html +23 -13
- package/dashboard/slim/js/knowledge.js +576 -0
- package/dashboard/slim/js/members.js +43 -0
- package/dashboard/slim/js/modals-tiles.js +6 -4
- package/dashboard/slim/js/pinned.js +7 -19
- package/dashboard/slim/js/status.js +17 -21
- package/dashboard/slim/styles.css +94 -36
- package/dashboard/styles.css +34 -0
- package/dashboard-build.js +1 -1
- package/dashboard.js +50 -2
- package/engine/lifecycle.js +6 -0
- package/engine/pipeline.js +10 -0
- package/engine/queries.js +81 -0
- package/engine/scheduler.js +19 -1
- package/package.json +1 -1
package/dashboard/js/utils.js
CHANGED
|
@@ -80,24 +80,256 @@ function _togglePinAndRefresh(key, source) {
|
|
|
80
80
|
else if (source === 'kb') renderKnowledgeBase({ preserveScroll: false });
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
// Modal navigation stack —
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
83
|
+
// Modal navigation stack — frames for nested modal layers (P-ce1e5e47)
|
|
84
|
+
// _modalStack holds breadcrumb frames the user can navigate BACK to.
|
|
85
|
+
// _currentVisibleArtifact tracks the currently-shown modal's identity (set by
|
|
86
|
+
// openArtifact / pushModalFrame). Direct row-click openers may leave this null;
|
|
87
|
+
// the back stack still works via the legacy pushModalBack snapshot path.
|
|
88
|
+
//
|
|
89
|
+
// Each frame: { type, id, scrollY, titleHtml, bodyHtml, openFn, openArgs }.
|
|
90
|
+
// Frames whose type starts with "_" (e.g. _legacy, _direct) are opaque
|
|
91
|
+
// breadcrumb-only frames that are NOT mirrored to the URL hash and are NOT
|
|
92
|
+
// replayed on refresh — they exist only for in-session back navigation.
|
|
93
|
+
var MODAL_STACK_MAX_DEPTH = 5;
|
|
94
|
+
var _modalStack = [];
|
|
95
|
+
var _currentVisibleArtifact = null;
|
|
96
|
+
var _legacyFrameSeq = 0;
|
|
97
|
+
var _suppressHistoryFromPopstate = false;
|
|
98
|
+
|
|
99
|
+
function _captureModalSnapshot() {
|
|
100
|
+
var titleEl = document.getElementById('modal-title');
|
|
101
|
+
var bodyEl = document.getElementById('modal-body');
|
|
102
|
+
return {
|
|
103
|
+
titleHtml: titleEl ? titleEl.innerHTML : '',
|
|
104
|
+
bodyHtml: bodyEl ? bodyEl.innerHTML : '',
|
|
105
|
+
scrollY: bodyEl ? (bodyEl.scrollTop || 0) : 0,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function _modalIsOpen() {
|
|
109
|
+
var el = document.getElementById('modal');
|
|
110
|
+
return !!(el && el.classList.contains('open'));
|
|
111
|
+
}
|
|
112
|
+
function _modalStackDepth() {
|
|
113
|
+
return _modalStack.length + (_currentVisibleArtifact ? 1 : 0);
|
|
114
|
+
}
|
|
115
|
+
function _capModalStackDepth() {
|
|
116
|
+
// Cap at MODAL_STACK_MAX_DEPTH: a 6th push evicts the OLDEST breadcrumb
|
|
117
|
+
// (index 0 of _modalStack), never the visible top.
|
|
118
|
+
while (_modalStackDepth() > MODAL_STACK_MAX_DEPTH && _modalStack.length > 0) {
|
|
119
|
+
_modalStack.shift();
|
|
120
|
+
}
|
|
89
121
|
}
|
|
90
|
-
function
|
|
91
|
-
var
|
|
122
|
+
function _updateModalChrome() {
|
|
123
|
+
var depth = _modalStackDepth();
|
|
92
124
|
var btn = document.getElementById('modal-back-btn');
|
|
93
|
-
if (btn
|
|
94
|
-
|
|
125
|
+
if (btn) btn.style.display = depth > 1 ? '' : 'none';
|
|
126
|
+
var chip = document.getElementById('modal-stack-chip');
|
|
127
|
+
if (chip) {
|
|
128
|
+
if (depth > 1) {
|
|
129
|
+
chip.style.display = '';
|
|
130
|
+
chip.textContent = depth + '/' + MODAL_STACK_MAX_DEPTH;
|
|
131
|
+
} else {
|
|
132
|
+
chip.style.display = 'none';
|
|
133
|
+
}
|
|
134
|
+
}
|
|
95
135
|
}
|
|
96
|
-
function
|
|
97
|
-
|
|
98
|
-
var
|
|
99
|
-
|
|
136
|
+
function _buildModalHash() {
|
|
137
|
+
var entries = [];
|
|
138
|
+
for (var i = 0; i < _modalStack.length; i++) {
|
|
139
|
+
var f = _modalStack[i];
|
|
140
|
+
if (f && f.type && f.id && String(f.type).charAt(0) !== '_') {
|
|
141
|
+
entries.push(encodeURIComponent(f.type) + ':' + encodeURIComponent(f.id));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (_currentVisibleArtifact && _currentVisibleArtifact.type && _currentVisibleArtifact.id) {
|
|
145
|
+
entries.push(
|
|
146
|
+
encodeURIComponent(_currentVisibleArtifact.type) + ':' +
|
|
147
|
+
encodeURIComponent(_currentVisibleArtifact.id)
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
var base = window.location.pathname + window.location.search;
|
|
151
|
+
if (entries.length === 0) return base;
|
|
152
|
+
// Cap URL to last MODAL_STACK_MAX_DEPTH entries (truncate oldest)
|
|
153
|
+
if (entries.length > MODAL_STACK_MAX_DEPTH) entries = entries.slice(-MODAL_STACK_MAX_DEPTH);
|
|
154
|
+
// P-3ed68b1e — frame separator is ',' (not '>'); '>' is in the WHATWG URL
|
|
155
|
+
// fragment percent-encode set and Chromium rewrites it to '%3E' on
|
|
156
|
+
// history.pushState, which broke deep-link replay (split('>') saw zero
|
|
157
|
+
// separators). ',' survives history serialisation intact, and since type
|
|
158
|
+
// + id are individually encodeURIComponent'd (',' itself encodes to
|
|
159
|
+
// '%2C'), no frame field can collide with the separator.
|
|
160
|
+
return base + '#modal=' + entries.join(',');
|
|
161
|
+
}
|
|
162
|
+
function _serializeStackForHistory() {
|
|
163
|
+
return {
|
|
164
|
+
breadcrumbs: _modalStack.map(function(f) { return { type: f.type, id: f.id }; }),
|
|
165
|
+
current: _currentVisibleArtifact
|
|
166
|
+
? { type: _currentVisibleArtifact.type, id: _currentVisibleArtifact.id }
|
|
167
|
+
: null,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function _pushModalHistoryState() {
|
|
171
|
+
if (_suppressHistoryFromPopstate) return;
|
|
172
|
+
try {
|
|
173
|
+
var state = { page: (typeof currentPage !== 'undefined' ? currentPage : null), modal: _serializeStackForHistory() };
|
|
174
|
+
history.pushState(state, '', _buildModalHash());
|
|
175
|
+
} catch { /* history disabled (e.g. file://) — fall back to in-memory stack */ }
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function pushModalFrame(frame) {
|
|
179
|
+
// frame: { type, id, openFn?, openArgs? } — describes the NEW visible modal.
|
|
180
|
+
// The current visible (if any) is snapshotted and pushed onto _modalStack as
|
|
181
|
+
// a breadcrumb; scrollY is captured here so popping restores the user's
|
|
182
|
+
// place in the parent view.
|
|
183
|
+
if (_currentVisibleArtifact) {
|
|
184
|
+
var snap = _captureModalSnapshot();
|
|
185
|
+
_modalStack.push({
|
|
186
|
+
type: _currentVisibleArtifact.type,
|
|
187
|
+
id: _currentVisibleArtifact.id,
|
|
188
|
+
titleHtml: snap.titleHtml,
|
|
189
|
+
bodyHtml: snap.bodyHtml,
|
|
190
|
+
scrollY: snap.scrollY,
|
|
191
|
+
openFn: _currentVisibleArtifact.openFn || null,
|
|
192
|
+
openArgs: _currentVisibleArtifact.openArgs || null,
|
|
193
|
+
});
|
|
194
|
+
} else if (_modalIsOpen()) {
|
|
195
|
+
// Modal was direct-opened (row click) without going through openArtifact.
|
|
196
|
+
// Snapshot it as an opaque _direct breadcrumb so Back still restores it.
|
|
197
|
+
var snap2 = _captureModalSnapshot();
|
|
198
|
+
_modalStack.push({
|
|
199
|
+
type: '_direct',
|
|
200
|
+
id: '_direct_' + (++_legacyFrameSeq),
|
|
201
|
+
titleHtml: snap2.titleHtml,
|
|
202
|
+
bodyHtml: snap2.bodyHtml,
|
|
203
|
+
scrollY: snap2.scrollY,
|
|
204
|
+
openFn: null,
|
|
205
|
+
openArgs: null,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
_currentVisibleArtifact = {
|
|
209
|
+
type: frame.type,
|
|
210
|
+
id: frame.id,
|
|
211
|
+
openFn: typeof frame.openFn === 'function' ? frame.openFn : null,
|
|
212
|
+
openArgs: Array.isArray(frame.openArgs) ? frame.openArgs : null,
|
|
213
|
+
};
|
|
214
|
+
_capModalStackDepth();
|
|
215
|
+
_updateModalChrome();
|
|
216
|
+
_pushModalHistoryState();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function popModalFrame() {
|
|
220
|
+
// Esc / X / back-btn / browser Back all funnel here. Use history.back() so
|
|
221
|
+
// the popstate listener is the single source of truth for stack diffing.
|
|
222
|
+
if (_modalStackDepth() === 0) {
|
|
223
|
+
if (_modalIsOpen()) _physicallyCloseModal();
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
try { history.back(); }
|
|
227
|
+
catch { _popOneFrameInternal(); _updateModalChrome(); }
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function peekTopFrame() {
|
|
231
|
+
if (_currentVisibleArtifact) {
|
|
232
|
+
return Object.assign({}, _currentVisibleArtifact);
|
|
233
|
+
}
|
|
234
|
+
if (_modalStack.length > 0) {
|
|
235
|
+
return Object.assign({}, _modalStack[_modalStack.length - 1]);
|
|
236
|
+
}
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function resetModalStack() {
|
|
241
|
+
_modalStack = [];
|
|
242
|
+
_currentVisibleArtifact = null;
|
|
243
|
+
_updateModalChrome();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function withTopFrame(type, id, fn) {
|
|
247
|
+
// Guard for per-modal poll bodies: only invoke `fn` when the named frame is
|
|
248
|
+
// still the visible top, so a stale poll on a lower frame can't clobber the
|
|
249
|
+
// user's currently-stacked modal. When no stack is tracked (direct-opened
|
|
250
|
+
// modal — _currentVisibleArtifact is null and _modalStack is empty) the
|
|
251
|
+
// poll proceeds unconditionally; row-click openers haven't routed through
|
|
252
|
+
// openArtifact, so we have no identity to check.
|
|
253
|
+
var top = peekTopFrame();
|
|
254
|
+
if (top && (top.type !== type || String(top.id) !== String(id))) return false;
|
|
255
|
+
try { fn(); } catch (e) { try { console.error(e); } catch {} }
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function _restoreFrameToVisible(frame) {
|
|
260
|
+
// Synchronously restore a snapshotted frame as the visible modal layer.
|
|
261
|
+
// scrollY is restored after a double-rAF so layout settles first.
|
|
262
|
+
var titleEl = document.getElementById('modal-title');
|
|
263
|
+
var bodyEl = document.getElementById('modal-body');
|
|
264
|
+
if (titleEl) {
|
|
265
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: snapshot of previously-rendered (already-escaped) modal title HTML; round-trip restore only
|
|
266
|
+
titleEl.innerHTML = frame.titleHtml || '';
|
|
267
|
+
}
|
|
268
|
+
if (bodyEl) {
|
|
269
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: snapshot of previously-rendered (already-escaped) modal body HTML; round-trip restore only
|
|
270
|
+
bodyEl.innerHTML = frame.bodyHtml || '';
|
|
271
|
+
}
|
|
272
|
+
var modalEl = document.getElementById('modal');
|
|
273
|
+
if (modalEl && !modalEl.classList.contains('open')) modalEl.classList.add('open');
|
|
274
|
+
requestAnimationFrame(function() {
|
|
275
|
+
requestAnimationFrame(function() {
|
|
276
|
+
var b = document.getElementById('modal-body');
|
|
277
|
+
if (b) b.scrollTop = frame.scrollY || 0;
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function _popOneFrameInternal() {
|
|
283
|
+
// Pop one breadcrumb and restore it as the visible modal. Used by the
|
|
284
|
+
// popstate handler (and as a fallback when history.back is unavailable).
|
|
285
|
+
if (_modalStack.length === 0) {
|
|
286
|
+
// Nothing to fall back to — close the modal entirely.
|
|
287
|
+
_currentVisibleArtifact = null;
|
|
288
|
+
if (_modalIsOpen()) _physicallyCloseModal();
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
var frame = _modalStack.pop();
|
|
292
|
+
_currentVisibleArtifact = {
|
|
293
|
+
type: frame.type,
|
|
294
|
+
id: frame.id,
|
|
295
|
+
openFn: frame.openFn,
|
|
296
|
+
openArgs: frame.openArgs,
|
|
297
|
+
};
|
|
298
|
+
_restoreFrameToVisible(frame);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function _physicallyCloseModal() {
|
|
302
|
+
// Close the modal DOM without going through closeModal() (which would itself
|
|
303
|
+
// call popModalFrame and recurse). Mirrors the visible side-effects of
|
|
304
|
+
// closeModal but skips stack management.
|
|
305
|
+
var modalEl = document.getElementById('modal');
|
|
306
|
+
if (modalEl) modalEl.classList.remove('open');
|
|
307
|
+
var inner = document.querySelector('#modal .modal');
|
|
308
|
+
if (inner) inner.classList.remove('modal-wide');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ─── Deprecation shims (legacy back-stack API) ───────────────────────────────
|
|
312
|
+
// Existing callers in render-work-items.js and render-pipelines.js still use
|
|
313
|
+
// pushModalBack(closure); on Back the closure re-renders the prior modal.
|
|
314
|
+
// We translate this into a breadcrumb push that carries the snapshot of the
|
|
315
|
+
// outgoing modal, so the new popstate-driven Back returns the same view.
|
|
316
|
+
function pushModalBack(reopenFn) {
|
|
317
|
+
var snap = _captureModalSnapshot();
|
|
318
|
+
_modalStack.push({
|
|
319
|
+
type: '_legacy',
|
|
320
|
+
id: '_legacy_' + (++_legacyFrameSeq),
|
|
321
|
+
titleHtml: snap.titleHtml,
|
|
322
|
+
bodyHtml: snap.bodyHtml,
|
|
323
|
+
scrollY: snap.scrollY,
|
|
324
|
+
openFn: typeof reopenFn === 'function' ? reopenFn : null,
|
|
325
|
+
openArgs: null,
|
|
326
|
+
});
|
|
327
|
+
_capModalStackDepth();
|
|
328
|
+
_updateModalChrome();
|
|
329
|
+
_pushModalHistoryState();
|
|
100
330
|
}
|
|
331
|
+
function modalGoBack() { popModalFrame(); }
|
|
332
|
+
function clearModalBackStack() { resetModalStack(); }
|
|
101
333
|
|
|
102
334
|
function updateModalPinBtn() {
|
|
103
335
|
var btn = document.getElementById('modal-pin-btn');
|
package/dashboard/layout.html
CHANGED
|
@@ -127,6 +127,8 @@
|
|
|
127
127
|
<div class="modal-header">
|
|
128
128
|
<h3 id="modal-title">—</h3>
|
|
129
129
|
<div class="modal-header-actions">
|
|
130
|
+
<button class="modal-copy modal-back-btn" id="modal-back-btn" onclick="popModalFrame()" title="Back to previous modal" style="display:none"><svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor"><path d="M9.78 12.78a.75.75 0 01-1.06 0L4.47 8.53a.75.75 0 010-1.06l4.25-4.25a.75.75 0 011.06 1.06L6.06 8l3.72 3.72a.75.75 0 010 1.06z"/></svg> Back</button>
|
|
131
|
+
<span class="modal-stack-chip" id="modal-stack-chip" title="Modal stack depth" style="display:none">1/5</span>
|
|
130
132
|
<button class="modal-copy" id="modal-edit-btn" onclick="modalToggleEdit()" title="Edit" style="display:none"><svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor"><path d="M11.013 1.427a1.75 1.75 0 012.474 0l1.086 1.086a1.75 1.75 0 010 2.474l-8.61 8.61c-.21.21-.47.364-.756.445l-3.251.93a.75.75 0 01-.927-.928l.929-3.25c.081-.286.235-.547.445-.758l8.61-8.61zM12.9 2.97L4.288 11.58l-.537 1.878 1.878-.537L14.242 4.31 12.9 2.97z"/></svg> Edit</button>
|
|
131
133
|
<button class="modal-copy is-success" id="modal-save-btn" onclick="modalSaveEdit()" title="Save" style="display:none"><svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor"><path d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"/></svg> Save</button>
|
|
132
134
|
<button class="modal-copy" id="modal-cancel-edit-btn" onclick="modalCancelEdit()" title="Cancel edit" style="display:none">Cancel</button>
|
package/dashboard/slim/body.html
CHANGED
|
@@ -106,11 +106,11 @@
|
|
|
106
106
|
<div class="cockpit-value dim">0</div>
|
|
107
107
|
<div class="cockpit-detail">no watches set</div>
|
|
108
108
|
</div>
|
|
109
|
-
<div class="cockpit-tile" data-tile="
|
|
109
|
+
<div class="cockpit-tile" data-tile="knowledge">
|
|
110
110
|
<button id="slim-tile-pin-chip" class="linkpr-chip on-tile" type="button" title="Pin content">+ Pin</button>
|
|
111
|
-
<div class="cockpit-label"><span class="cockpit-dot"></span>
|
|
111
|
+
<div class="cockpit-label"><span class="cockpit-dot"></span> Knowledge</div>
|
|
112
112
|
<div class="cockpit-value dim">0</div>
|
|
113
|
-
<div class="cockpit-detail">
|
|
113
|
+
<div class="cockpit-detail">pinned context, notes & KB</div>
|
|
114
114
|
</div>
|
|
115
115
|
</div>
|
|
116
116
|
</div>
|
|
@@ -187,18 +187,28 @@
|
|
|
187
187
|
</div>
|
|
188
188
|
</div>
|
|
189
189
|
|
|
190
|
-
<!--
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
190
|
+
<!-- Knowledge control panel — opened from the "Knowledge" status tile. One
|
|
191
|
+
unified box surfacing all three knowledge surfaces via tabs: Pinned
|
|
192
|
+
Context, Notes, and KB (knowledge base). Each tab is rendered lazily by
|
|
193
|
+
dashboard/slim/js/knowledge.js into #slim-knowledge-body. Backed by the
|
|
194
|
+
existing endpoints (no new server routes):
|
|
195
|
+
Pinned — /api/pinned (+ update/remove), surfaced via /api/status.
|
|
196
|
+
Notes — /api/notes-full (read), /api/notes-save (edit notes.md),
|
|
197
|
+
/api/notes (add an inbox note).
|
|
198
|
+
KB — /api/knowledge (list), /api/knowledge/:cat/:file (read),
|
|
199
|
+
/api/knowledge POST (create), /api/kb-pins(+/toggle). -->
|
|
200
|
+
<div class="modal-bg" id="slim-knowledge-modal">
|
|
201
|
+
<div class="modal slim-knowledge-modal-inner">
|
|
196
202
|
<div class="modal-header">
|
|
197
|
-
<h3>
|
|
198
|
-
<
|
|
199
|
-
|
|
203
|
+
<h3>Knowledge</h3>
|
|
204
|
+
<div class="kn-tabs" id="slim-kn-tabs" role="tablist">
|
|
205
|
+
<button class="kn-tab active" id="slim-kn-tab-pinned" data-kn-tab="pinned" type="button" role="tab" aria-selected="true" aria-controls="slim-knowledge-body">Pinned Context</button>
|
|
206
|
+
<button class="kn-tab" id="slim-kn-tab-notes" data-kn-tab="notes" type="button" role="tab" aria-selected="false" aria-controls="slim-knowledge-body">Notes</button>
|
|
207
|
+
<button class="kn-tab" id="slim-kn-tab-kb" data-kn-tab="kb" type="button" role="tab" aria-selected="false" aria-controls="slim-knowledge-body">KB</button>
|
|
208
|
+
</div>
|
|
209
|
+
<button id="slim-knowledge-close" class="icon-btn" title="Close" style="margin-left:auto">×</button>
|
|
200
210
|
</div>
|
|
201
|
-
<div class="modal-body" id="slim-
|
|
211
|
+
<div class="modal-body" id="slim-knowledge-body" role="tabpanel" tabindex="0" aria-labelledby="slim-kn-tab-pinned"></div>
|
|
202
212
|
</div>
|
|
203
213
|
</div>
|
|
204
214
|
|