@yemi33/minions 0.1.2058 → 0.1.2060
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
CHANGED
|
@@ -4,6 +4,11 @@ function closeModal() {
|
|
|
4
4
|
const modalEl = document.querySelector('#modal .modal');
|
|
5
5
|
if (modalEl) modalEl.classList.remove('modal-wide');
|
|
6
6
|
document.getElementById('modal').classList.remove('open');
|
|
7
|
+
// Clear the WI-detail auto-refresh tracker so renderWorkItems stops
|
|
8
|
+
// rewriting #modal-body each tick. Safe to do unconditionally — if the
|
|
9
|
+
// closed modal wasn't a WI detail, these vars were already null.
|
|
10
|
+
if (typeof _wiModalOpenId !== 'undefined') { _wiModalOpenId = null; }
|
|
11
|
+
if (typeof _wiModalHydratedFields !== 'undefined') { _wiModalHydratedFields = null; }
|
|
7
12
|
clearModalBackStack();
|
|
8
13
|
// Hide Q&A section (only shown for document modals)
|
|
9
14
|
document.getElementById('modal-qa').style.display = 'none';
|
package/dashboard/js/refresh.js
CHANGED
|
@@ -294,20 +294,38 @@ function _processStatusUpdate(data) {
|
|
|
294
294
|
window._lastStatus = data;
|
|
295
295
|
|
|
296
296
|
|
|
297
|
-
// Render
|
|
298
|
-
//
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
//
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
// the
|
|
297
|
+
// Render every section every tick (W-mpn7keq9000302c9 + this commit).
|
|
298
|
+
// Every `_changed(...)` GATE was producing the same class of bug —
|
|
299
|
+
// ref-equality or JSON.stringify short-circuit falsely matching across
|
|
300
|
+
// ticks, leaving the DOM frozen until the user did a hard refresh. The
|
|
301
|
+
// gates were a small CPU win at the cost of real-time correctness on
|
|
302
|
+
// every screen.
|
|
303
|
+
//
|
|
304
|
+
// We KEEP the `_changed(...)` CALLS (their side effect of populating
|
|
305
|
+
// `_lastChangedFlags` is still load-bearing for the diag ring-buffer
|
|
306
|
+
// and for the `_workItemsChanged` / `_prsChanged` cross-slice triggers
|
|
307
|
+
// below — F1 / F3 / W-mpgb0xbh000e3b86) but no longer use the return
|
|
308
|
+
// value to gate the render. Each renderer is a contained DOM rewrite
|
|
309
|
+
// (~ a few KB of HTML); ~10–20 of them per 4s tick is well under one
|
|
310
|
+
// frame's budget.
|
|
311
|
+
//
|
|
312
|
+
// Gates intentionally kept: none on the render path. The render-versions
|
|
313
|
+
// bump path (RENDER_VERSIONS map + _changed's stringify cache) is still
|
|
314
|
+
// useful for the diag flag, just not as a render skip.
|
|
305
315
|
_changed('agents', data.agents);
|
|
306
316
|
renderAgents(data.agents);
|
|
307
317
|
cmdUpdateAgentList(data.agents);
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
318
|
+
// prdProgress + prdPrs are captured together so both flags publish to
|
|
319
|
+
// the diag buffer; the renderer + cachePrdItems run unconditionally.
|
|
320
|
+
_changed('prdProgress', data.prdProgress);
|
|
321
|
+
_changed('prdPrs', data.pullRequests?.length);
|
|
322
|
+
renderPrdProgress(data.prdProgress);
|
|
323
|
+
_cachePrdItems(data.prdProgress);
|
|
324
|
+
_changed('inbox', data.inbox);
|
|
325
|
+
renderInbox(data.inbox || []);
|
|
326
|
+
_changed('projects', data.projects);
|
|
327
|
+
cmdUpdateProjectList(data.projects || []);
|
|
328
|
+
renderProjects(data.projects || []);
|
|
311
329
|
// FRE banner — safe to call every tick (idempotent + cheap). Pass the full
|
|
312
330
|
// status payload so the runtime-CLI explainer reads autoMode.defaultCli from
|
|
313
331
|
// THIS tick (window._lastStatus is hoisted above, but renderFre takes the
|
|
@@ -315,47 +333,55 @@ function _processStatusUpdate(data) {
|
|
|
315
333
|
if (typeof renderFre === 'function') {
|
|
316
334
|
try { renderFre(data); } catch { /* expected on first load */ }
|
|
317
335
|
}
|
|
318
|
-
|
|
319
|
-
|
|
336
|
+
_changed('notes', data.notes);
|
|
337
|
+
renderNotes(data.notes);
|
|
338
|
+
_changed('prd', [data.prd, data.prdProgress]);
|
|
339
|
+
renderPrd(data.prd, data.prdProgress);
|
|
320
340
|
// Capture prs + workItems change signals once — also reused by the cross-slice
|
|
321
341
|
// render triggers at the bottom of this function (F1/F3, W-mpgb0xbh000e3b86).
|
|
322
342
|
// _changed mutates _sectionCache so it must be called exactly once per key.
|
|
323
343
|
var _prsChanged = _changed('prs', data.pullRequests);
|
|
324
344
|
var _workItemsChanged = _changed('workItems', data.workItems);
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
345
|
+
renderPrs(data.pullRequests || []);
|
|
346
|
+
_changed('archivedPrds', data.archivedPrds);
|
|
347
|
+
renderArchiveButtons(data.archivedPrds || []);
|
|
348
|
+
_changed('engine', data.engine);
|
|
349
|
+
if (data.engine) renderEngineStatus(data.engine);
|
|
350
|
+
var qs = document.getElementById('engine-quick-stats');
|
|
351
|
+
if (qs && data.engine) {
|
|
352
|
+
var wt = data.engine.worktreeCount != null ? data.engine.worktreeCount : '-';
|
|
353
|
+
var pid = data.engine.pid || '-';
|
|
354
|
+
// W-mpnc4u8c001d9d6c — replace the dead "Tick: -" chip (control.json
|
|
355
|
+
// never carried a `tick` field) with a live "Next tick in Xs" countdown
|
|
356
|
+
// driven by engine.lastTickAt (stamped at the start of every tickInner)
|
|
357
|
+
// and engine.tickInterval (config, surfaced in the status payload).
|
|
358
|
+
// _updateNextTickChip below ticks the inner span every 1s without
|
|
359
|
+
// re-rendering this whole row.
|
|
360
|
+
_engineCountdown.lastTickAt = Number(data.engine.lastTickAt) || 0;
|
|
361
|
+
_engineCountdown.tickInterval = Number(data.engine.tickInterval) || 0;
|
|
362
|
+
_engineCountdown.engineState = data.engine.state || 'stopped';
|
|
363
|
+
// Feed the cadence ring buffer so the overshoot label can surface the
|
|
364
|
+
// observed tick-to-tick gap (W-mpodheao0006a37a).
|
|
365
|
+
_recordEngineTickObservation(_engineCountdown.lastTickAt);
|
|
366
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: composed from internal engine metrics (pid, lastTickAt/tickInterval, worktreeCount) and a literal id; no user data flows in
|
|
367
|
+
qs.innerHTML = '<span>PID: <b>' + pid + '</b></span>' +
|
|
368
|
+
'<span>Next tick in: <b id="engine-next-tick">' + _formatNextTickText() + '</b></span>' +
|
|
369
|
+
'<span>Worktrees: <b>' + wt + '</b></span>';
|
|
370
|
+
_startNextTickTicker();
|
|
351
371
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
372
|
+
_changed('version', data.version);
|
|
373
|
+
renderVersionBanner(data.version);
|
|
374
|
+
_changed('adoThrottle', data.adoThrottle);
|
|
375
|
+
renderAdoThrottleAlert(data.adoThrottle);
|
|
376
|
+
_changed('ghThrottle', data.ghThrottle);
|
|
377
|
+
renderGhThrottleAlert(data.ghThrottle);
|
|
378
|
+
_changed('dispatch', data.dispatch);
|
|
379
|
+
renderDispatch(data.dispatch);
|
|
356
380
|
prunePrdRequeueState(window._lastWorkItems);
|
|
357
|
-
|
|
358
|
-
|
|
381
|
+
_changed('engineLog', data.engineLog);
|
|
382
|
+
renderEngineLog(data.engineLog || []);
|
|
383
|
+
_changed('metrics', data.metrics);
|
|
384
|
+
renderMetrics(data.metrics || {});
|
|
359
385
|
// managed-processes panel — ETag-gated so unchanged ticks return 304 with
|
|
360
386
|
// no body (P-6e2a8b13). Sequenced BEFORE the keep-processes call below via
|
|
361
387
|
// .then() so the keep renderer reads a populated managed-PID cache for
|
|
@@ -374,14 +400,21 @@ function _processStatusUpdate(data) {
|
|
|
374
400
|
.catch(function () { /* keep render even if managed fetch failed — getLastItems() returns the last good cache (or []) */ })
|
|
375
401
|
.then(function () { try { renderKeepProcesses(); } catch {} });
|
|
376
402
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
403
|
+
renderWorkItems(data.workItems || []);
|
|
404
|
+
_changed('skills', data.skills);
|
|
405
|
+
renderSkills(data.skills || []);
|
|
406
|
+
_changed('mcpServers', data.mcpServers);
|
|
407
|
+
renderMcpServers(data.mcpServers || []);
|
|
408
|
+
_changed('schedules', data.schedules);
|
|
409
|
+
renderSchedules(data.schedules || []);
|
|
410
|
+
_changed('watches', data.watches);
|
|
411
|
+
renderWatches(data.watches || []);
|
|
412
|
+
_changed('meetings', data.meetings);
|
|
413
|
+
renderMeetings(data.meetings || []);
|
|
414
|
+
_changed('pipelines', data.pipelines);
|
|
415
|
+
if (typeof renderPipelines === 'function') renderPipelines(data.pipelines || []);
|
|
416
|
+
_changed('pinned', data.pinned);
|
|
417
|
+
renderPinned(data.pinned || []);
|
|
385
418
|
// Sidebar counts (cheap)
|
|
386
419
|
const swi = document.getElementById('sidebar-wi');
|
|
387
420
|
if (swi) swi.textContent = (data.workItems || []).length || '';
|
|
@@ -4,6 +4,18 @@ let allWorkItems = [];
|
|
|
4
4
|
let wiPage = 0;
|
|
5
5
|
const WI_PER_PAGE = 20;
|
|
6
6
|
|
|
7
|
+
// Track open WI detail modal so renderWorkItems can re-render its body
|
|
8
|
+
// every poll tick. Without this the modal stays frozen on the snapshot
|
|
9
|
+
// from the moment it was opened — status flips / agent assignments /
|
|
10
|
+
// PR link arrival are invisible until the user closes + reopens or
|
|
11
|
+
// hard-refreshes. Mirrors the same fix that drove the section-render
|
|
12
|
+
// gate sweep (8ad48509 / W-mpn7keq9000302c9). `_wiModalHydratedFields`
|
|
13
|
+
// caches the heavy free-text fields (description, acceptanceCriteria,
|
|
14
|
+
// references) loaded once by openWorkItemDetail's GET /api/work-items/<id>
|
|
15
|
+
// hydration call so per-tick re-renders don't lose them.
|
|
16
|
+
let _wiModalOpenId = null;
|
|
17
|
+
let _wiModalHydratedFields = null;
|
|
18
|
+
|
|
7
19
|
// Track retry state per work item so loading/success/error survives re-renders
|
|
8
20
|
const _wiRetryState = {}; // { [id]: { status: 'pending'|'done'|'error', message?, until? } }
|
|
9
21
|
function setWiRetryState(id, state) { _wiRetryState[id] = state; }
|
|
@@ -155,6 +167,27 @@ function renderWorkItems(items) {
|
|
|
155
167
|
const newWrap = el.querySelector('.pr-table-wrap');
|
|
156
168
|
if (newWrap) newWrap.scrollLeft = savedScroll;
|
|
157
169
|
}
|
|
170
|
+
// Refresh the open WI detail modal in-place so its status badge,
|
|
171
|
+
// agent assignment, PR link, etc. reflect the latest /api/status slice.
|
|
172
|
+
// The heavy free-text fields (description, AC, references) live in
|
|
173
|
+
// `_wiModalHydratedFields` from the one-time GET /api/work-items/<id>
|
|
174
|
+
// hydration and survive across these re-renders.
|
|
175
|
+
if (_wiModalOpenId) {
|
|
176
|
+
const slim = items.find(i => i.id === _wiModalOpenId);
|
|
177
|
+
if (slim) {
|
|
178
|
+
const merged = Object.assign({}, _wiModalHydratedFields || {}, slim);
|
|
179
|
+
const body = document.getElementById('modal-body');
|
|
180
|
+
if (body) {
|
|
181
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() or renderMd() by _wiRenderDetail()
|
|
182
|
+
body.innerHTML = _wiRenderDetail(merged);
|
|
183
|
+
}
|
|
184
|
+
const title = document.getElementById('modal-title');
|
|
185
|
+
if (title) title.textContent = slim.title || slim.id;
|
|
186
|
+
}
|
|
187
|
+
// If the WI dropped out of the slim slice (deleted/archived), leave
|
|
188
|
+
// the modal as-is — the user will close it normally; we don't want
|
|
189
|
+
// to auto-dismiss in case they're still reading.
|
|
190
|
+
}
|
|
158
191
|
}
|
|
159
192
|
|
|
160
193
|
async function editWorkItem(id, source) {
|
|
@@ -649,6 +682,15 @@ function openWorkItemDetail(id) {
|
|
|
649
682
|
(cached.referencesCount > 0 && !Array.isArray(cached.references));
|
|
650
683
|
|
|
651
684
|
const initial = needsHydration ? Object.assign({}, cached, { _descriptionLoading: true }) : cached;
|
|
685
|
+
// Track which WI's modal is open so renderWorkItems can re-render the
|
|
686
|
+
// modal body each poll tick. Reset the hydrated cache; openWorkItemDetail
|
|
687
|
+
// is the only authoritative source of the heavy free-text fields.
|
|
688
|
+
_wiModalOpenId = id;
|
|
689
|
+
_wiModalHydratedFields = needsHydration ? null : {
|
|
690
|
+
description: cached.description,
|
|
691
|
+
acceptanceCriteria: Array.isArray(cached.acceptanceCriteria) ? cached.acceptanceCriteria : undefined,
|
|
692
|
+
references: Array.isArray(cached.references) ? cached.references : undefined,
|
|
693
|
+
};
|
|
652
694
|
document.getElementById('modal-title').textContent = initial.title || initial.id;
|
|
653
695
|
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() or renderMd() by _wiRenderDetail() (fields: title, description, agent, source, reasons, references, artifacts, PR links)
|
|
654
696
|
document.getElementById('modal-body').innerHTML = _wiRenderDetail(initial);
|
|
@@ -662,18 +704,22 @@ function openWorkItemDetail(id) {
|
|
|
662
704
|
.then(function(r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
|
|
663
705
|
.then(function(data) {
|
|
664
706
|
// Guard against modal navigation away from this WI during the fetch.
|
|
665
|
-
|
|
666
|
-
if (!title || title.textContent !== (initial.title || initial.id)) return;
|
|
707
|
+
if (_wiModalOpenId !== id) return;
|
|
667
708
|
var full = data && data.item;
|
|
668
709
|
if (!full) return;
|
|
710
|
+
// Cache the heavy free-text fields so per-tick re-renders preserve
|
|
711
|
+
// them. We keep them separate from the slim slice (which renderWorkItems
|
|
712
|
+
// refreshes on every tick).
|
|
713
|
+
_wiModalHydratedFields = {
|
|
714
|
+
description: full.description || cached.description || '',
|
|
715
|
+
acceptanceCriteria: Array.isArray(full.acceptanceCriteria) ? full.acceptanceCriteria : undefined,
|
|
716
|
+
references: Array.isArray(full.references) ? full.references : undefined,
|
|
717
|
+
};
|
|
669
718
|
// Merge: cached cross-slice fields (_pr, _artifacts, etc.) WIN over
|
|
670
719
|
// the on-disk record so we don't lose engine enrichment that lives
|
|
671
720
|
// only on the in-memory pass. The full record contributes description,
|
|
672
721
|
// acceptanceCriteria, and references back to the rendered shape.
|
|
673
|
-
var merged = Object.assign({}, full, cached);
|
|
674
|
-
merged.description = full.description || cached.description || '';
|
|
675
|
-
if (Array.isArray(full.acceptanceCriteria)) merged.acceptanceCriteria = full.acceptanceCriteria;
|
|
676
|
-
if (Array.isArray(full.references)) merged.references = full.references;
|
|
722
|
+
var merged = Object.assign({}, full, cached, _wiModalHydratedFields);
|
|
677
723
|
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() or renderMd() by _wiRenderDetail() (fields: title, description, agent, source, reasons, references, artifacts, PR links)
|
|
678
724
|
document.getElementById('modal-body').innerHTML = _wiRenderDetail(merged);
|
|
679
725
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2060",
|
|
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"
|