@yemi33/minions 0.1.2015 → 0.1.2017
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/qa.js +11 -22
- package/dashboard/js/refresh.js +105 -6
- package/dashboard/js/render-kb.js +9 -1
- package/dashboard/js/render-meetings.js +1 -1
- package/dashboard/js/render-pipelines.js +34 -1
- package/dashboard/js/render-plans.js +10 -2
- package/dashboard/js/state.js +43 -5
- package/dashboard.js +11 -0
- package/package.json +1 -1
package/dashboard/js/qa.js
CHANGED
|
@@ -465,28 +465,17 @@ function qaOpenRunAgent(workItemId, agentId) {
|
|
|
465
465
|
}
|
|
466
466
|
|
|
467
467
|
// ── Page-navigation hooks ──────────────────────────────────────────────────
|
|
468
|
-
// switchPage
|
|
469
|
-
//
|
|
470
|
-
//
|
|
471
|
-
//
|
|
472
|
-
//
|
|
473
|
-
//
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
try { _stopQaRunsPoll(); } catch {}
|
|
480
|
-
const ret = _origSwitchPage(page, pushState);
|
|
481
|
-
if (page === 'qa') {
|
|
482
|
-
try { loadQaTargets(); } catch {}
|
|
483
|
-
try { loadQaRunbooks(); } catch {}
|
|
484
|
-
try { _startQaRunsPoll(); } catch {}
|
|
485
|
-
}
|
|
486
|
-
return ret;
|
|
487
|
-
};
|
|
488
|
-
window.switchPage.__qaWrapped = true;
|
|
489
|
-
})();
|
|
468
|
+
// W-mpgb0xa7000d90d4 — the per-tab switchPage monkey-patch this file used to
|
|
469
|
+
// install (__qaWrapped wrapper that called closeManagedLog + _stopQaRunsPoll
|
|
470
|
+
// on every navigation and kicked off loadQaTargets / loadQaRunbooks /
|
|
471
|
+
// _startQaRunsPoll on enter) has been generalized into one canonical
|
|
472
|
+
// lifecycle. See `PAGE_LAZY_LOADERS` + `PAGE_LEAVE_HOOKS` in state.js:
|
|
473
|
+
// - QA enter hooks live in PAGE_LAZY_LOADERS.qa
|
|
474
|
+
// - closeManagedLog + _stopQaRunsPoll live in PAGE_LEAVE_HOOKS (fire on
|
|
475
|
+
// every page transition — both are no-ops when nothing is active)
|
|
476
|
+
// The loader / stop functions themselves stay co-located with the QA UI here
|
|
477
|
+
// and are resolved by name through window[name] at switchPage time, so this
|
|
478
|
+
// file no longer needs to wrap switchPage.
|
|
490
479
|
|
|
491
480
|
// Refresh log previews after every runs render — _qaFillLogPreviews is a
|
|
492
481
|
// no-op for already-loaded blocks, so polling repeatedly is safe.
|
package/dashboard/js/refresh.js
CHANGED
|
@@ -26,12 +26,58 @@ function _detectPageChanges(data) {
|
|
|
26
26
|
return changes;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
// Change detection — skip renders for sections that haven't changed since last refresh
|
|
29
|
+
// Change detection — skip renders for sections that haven't changed since last refresh.
|
|
30
|
+
//
|
|
31
|
+
// RENDER_VERSIONS is the in-process cache-bust knob (R3, W-mpgb0xgc000hf1d3).
|
|
32
|
+
// _changed() caches JSON.stringify(value) per key, so when the input data is
|
|
33
|
+
// byte-identical between ticks the render is skipped. That's the right call
|
|
34
|
+
// 99% of the time, but it has a sharp edge: when a renderer body itself is
|
|
35
|
+
// edited (hot-reloaded via dashboard-build.js or a freshly shipped bundle)
|
|
36
|
+
// and the input stays the same, the stale render persists forever because
|
|
37
|
+
// the cache key still matches. F8 / "projects chip" class of bug.
|
|
38
|
+
//
|
|
39
|
+
// Bump the matching entry below whenever a renderer's *output* may change
|
|
40
|
+
// for the same input. Cross-restart safety lives in the dashboardBuildId
|
|
41
|
+
// reload path below — RENDER_VERSIONS handles the within-process case.
|
|
42
|
+
const RENDER_VERSIONS = {
|
|
43
|
+
agents: 1,
|
|
44
|
+
prdProgress: 1,
|
|
45
|
+
prdPrs: 1,
|
|
46
|
+
inbox: 1,
|
|
47
|
+
projects: 1,
|
|
48
|
+
notes: 1,
|
|
49
|
+
prd: 1,
|
|
50
|
+
prs: 1,
|
|
51
|
+
archivedPrds: 1,
|
|
52
|
+
engine: 1,
|
|
53
|
+
version: 1,
|
|
54
|
+
adoThrottle: 1,
|
|
55
|
+
ghThrottle: 1,
|
|
56
|
+
dispatch: 1,
|
|
57
|
+
engineLog: 1,
|
|
58
|
+
metrics: 1,
|
|
59
|
+
workItems: 1,
|
|
60
|
+
skills: 1,
|
|
61
|
+
mcpServers: 1,
|
|
62
|
+
schedules: 1,
|
|
63
|
+
watches: 1,
|
|
64
|
+
meetings: 1,
|
|
65
|
+
pipelines: 1,
|
|
66
|
+
pinned: 1,
|
|
67
|
+
};
|
|
30
68
|
const _sectionCache = {};
|
|
31
|
-
|
|
69
|
+
const _sectionCacheVersions = {};
|
|
70
|
+
function _changed(key, value, version) {
|
|
71
|
+
var v = version == null ? (RENDER_VERSIONS[key] || 0) : version;
|
|
72
|
+
// Drop the stale-version entry so the cache doesn't grow unbounded across bumps.
|
|
73
|
+
if (_sectionCacheVersions[key] !== undefined && _sectionCacheVersions[key] !== v) {
|
|
74
|
+
delete _sectionCache[key + ':v' + _sectionCacheVersions[key]];
|
|
75
|
+
}
|
|
76
|
+
_sectionCacheVersions[key] = v;
|
|
77
|
+
var cacheKey = key + ':v' + v;
|
|
32
78
|
var json = JSON.stringify(value);
|
|
33
|
-
if (_sectionCache[
|
|
34
|
-
_sectionCache[
|
|
79
|
+
if (_sectionCache[cacheKey] === json) return false;
|
|
80
|
+
_sectionCache[cacheKey] = json;
|
|
35
81
|
return true;
|
|
36
82
|
}
|
|
37
83
|
|
|
@@ -99,7 +145,12 @@ function _processStatusUpdate(data) {
|
|
|
99
145
|
}
|
|
100
146
|
if (_changed('notes', data.notes)) renderNotes(data.notes);
|
|
101
147
|
if (_changed('prd', [data.prd, data.prdProgress])) renderPrd(data.prd, data.prdProgress);
|
|
102
|
-
|
|
148
|
+
// Capture prs + workItems change signals once — also reused by the cross-slice
|
|
149
|
+
// render triggers at the bottom of this function (F1/F3, W-mpgb0xbh000e3b86).
|
|
150
|
+
// _changed mutates _sectionCache so it must be called exactly once per key.
|
|
151
|
+
var _prsChanged = _changed('prs', data.pullRequests);
|
|
152
|
+
var _workItemsChanged = _changed('workItems', data.workItems);
|
|
153
|
+
if (_prsChanged) renderPrs(data.pullRequests || []);
|
|
103
154
|
if (_changed('archivedPrds', data.archivedPrds)) renderArchiveButtons(data.archivedPrds || []);
|
|
104
155
|
if (_changed('engine', data.engine)) {
|
|
105
156
|
renderEngineStatus(data.engine);
|
|
@@ -136,7 +187,7 @@ function _processStatusUpdate(data) {
|
|
|
136
187
|
.catch(function () { /* keep render even if managed fetch failed — getLastItems() returns the last good cache (or []) */ })
|
|
137
188
|
.then(function () { try { renderKeepProcesses(); } catch {} });
|
|
138
189
|
}
|
|
139
|
-
if (
|
|
190
|
+
if (_workItemsChanged) renderWorkItems(data.workItems || []);
|
|
140
191
|
if (_changed('skills', data.skills)) renderSkills(data.skills || []);
|
|
141
192
|
if (_changed('mcpServers', data.mcpServers)) renderMcpServers(data.mcpServers || []);
|
|
142
193
|
if (_changed('schedules', data.schedules)) renderSchedules(data.schedules || []);
|
|
@@ -153,6 +204,27 @@ function _processStatusUpdate(data) {
|
|
|
153
204
|
if (!window._kbRefreshCount) window._kbRefreshCount = 0;
|
|
154
205
|
if (window._kbRefreshCount++ % 3 === 0) { refreshKnowledgeBase(); refreshPlans(); }
|
|
155
206
|
|
|
207
|
+
// Cross-slice render triggers (F1/F3, W-mpgb0xbh000e3b86): renderPrs reads
|
|
208
|
+
// window._lastWorkItems for the +N follow-up chip count and derivePlanStatus
|
|
209
|
+
// reads window._lastStatus.pullRequests for verify-follow-up reconciliation.
|
|
210
|
+
// When workItems OR pullRequests change, re-render the dependents against the
|
|
211
|
+
// freshest cached data — without waiting up to 12s for the next refreshPlans
|
|
212
|
+
// cycle (F3) or for a PR object to mutate (F1). Runs after the window._last*
|
|
213
|
+
// assignments above so the cached globals these renderers consult are fresh.
|
|
214
|
+
if (_workItemsChanged && !_prsChanged) {
|
|
215
|
+
// F1: only the work-item slice moved this tick — renderPrs wasn't called
|
|
216
|
+
// above, so the +N follow-up chip would otherwise stay stale until the
|
|
217
|
+
// next PR mutation.
|
|
218
|
+
renderPrs(data.pullRequests || []);
|
|
219
|
+
}
|
|
220
|
+
if ((_workItemsChanged || _prsChanged) && Array.isArray(window._lastPlans) && typeof renderPlans === 'function') {
|
|
221
|
+
// F3: derivePlanStatus + _renderVerifyBadge derive from pullRequests +
|
|
222
|
+
// workItems. Re-render against the cached plan list so plan status flips
|
|
223
|
+
// within one /api/status tick (~4s) instead of one refreshPlans poll
|
|
224
|
+
// (~12s). No-op when _lastPlans hasn't been populated yet (first load).
|
|
225
|
+
renderPlans(window._lastPlans);
|
|
226
|
+
}
|
|
227
|
+
|
|
156
228
|
// Sidebar activity indicators — show red dot on pages with new activity
|
|
157
229
|
try {
|
|
158
230
|
const changes = _detectPageChanges(data);
|
|
@@ -165,6 +237,14 @@ function _processStatusUpdate(data) {
|
|
|
165
237
|
}
|
|
166
238
|
|
|
167
239
|
let _knownDashboardStartId = null;
|
|
240
|
+
// Hard-reload trigger when the assembled dashboard HTML (and therefore any
|
|
241
|
+
// renderer body) has changed since the page was first loaded. Mirrors the
|
|
242
|
+
// _knownDashboardStartId pattern (R3, W-mpgb0xgc000hf1d3): catches restart-
|
|
243
|
+
// crossing renderer drift that RENDER_VERSIONS (in-process bump) can't see,
|
|
244
|
+
// since a server restart wipes the JS module identity entirely. data.version.
|
|
245
|
+
// dashboardBuildId is the md5 of the assembled HTML — bumps automatically on
|
|
246
|
+
// hot-reload + on cold restart with any /dashboard/** change.
|
|
247
|
+
let _knownDashboardBuildId = null;
|
|
168
248
|
// /api/status ETag cache (W-mpehsyhv0017085a). The dashboard polls every 4 s
|
|
169
249
|
// but the server-side cache only changes every 10–60 s. Sending If-None-Match
|
|
170
250
|
// lets the server short-circuit ~60 %+ of polls into a 304 with no body —
|
|
@@ -173,7 +253,15 @@ let _knownDashboardStartId = null;
|
|
|
173
253
|
// downstream consumers of `data`.
|
|
174
254
|
let _lastStatusEtag = null;
|
|
175
255
|
let _lastStatusData = null;
|
|
256
|
+
// In-flight gate (S5 — dashboard-refresh-audit). setInterval(refresh, 4000)
|
|
257
|
+
// can interleave two ticks inside _processStatusUpdate if a refresh takes
|
|
258
|
+
// >4 s (cold server cache + slow JSON + network blip). Skip the second tick
|
|
259
|
+
// when the first hasn't finished — the next interval fire picks up the
|
|
260
|
+
// fresh state anyway.
|
|
261
|
+
let _refreshInFlight = false;
|
|
176
262
|
async function refresh() {
|
|
263
|
+
if (_refreshInFlight) return;
|
|
264
|
+
_refreshInFlight = true;
|
|
177
265
|
try {
|
|
178
266
|
const headers = {};
|
|
179
267
|
if (_lastStatusEtag) headers['If-None-Match'] = _lastStatusEtag;
|
|
@@ -198,8 +286,19 @@ async function refresh() {
|
|
|
198
286
|
return;
|
|
199
287
|
}
|
|
200
288
|
if (dashId) _knownDashboardStartId = dashId;
|
|
289
|
+
// Auto-reload when the assembled dashboard HTML changed (renderer body or
|
|
290
|
+
// any other concat'd JS) — catches drift the in-process RENDER_VERSIONS
|
|
291
|
+
// bump can't see (R3, W-mpgb0xgc000hf1d3).
|
|
292
|
+
const buildId = (data.version && data.version.dashboardBuildId) || null;
|
|
293
|
+
if (buildId && _knownDashboardBuildId && buildId !== _knownDashboardBuildId) {
|
|
294
|
+
console.log('Dashboard build changed — reloading page');
|
|
295
|
+
location.reload();
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (buildId) _knownDashboardBuildId = buildId;
|
|
201
299
|
_processStatusUpdate(data);
|
|
202
300
|
} catch(e) { console.error('refresh error', e); }
|
|
301
|
+
finally { _refreshInFlight = false; }
|
|
203
302
|
}
|
|
204
303
|
|
|
205
304
|
refresh();
|
|
@@ -32,8 +32,16 @@ function _formatSweepElapsed(startedAt) {
|
|
|
32
32
|
|
|
33
33
|
async function refreshKnowledgeBase() {
|
|
34
34
|
try {
|
|
35
|
+
// Always update _kbData — cross-slice readers (e.g. command-center.js
|
|
36
|
+
// suggestion lookup) depend on it staying fresh even when the renderer is
|
|
37
|
+
// skipped (S1).
|
|
35
38
|
_kbData = await fetch('/api/knowledge').then(r => r.json());
|
|
36
|
-
|
|
39
|
+
// Skip the full kb-list/kb-tabs rewrite when /api/knowledge returned the
|
|
40
|
+
// same payload as last tick. Dedicated cache key 'kbPayload' avoids
|
|
41
|
+
// colliding with any other tracked slice (S1).
|
|
42
|
+
if (typeof _changed !== 'function' || _changed('kbPayload', _kbData)) {
|
|
43
|
+
renderKnowledgeBase();
|
|
44
|
+
}
|
|
37
45
|
} catch (e) { console.error('kb refresh:', e.message); }
|
|
38
46
|
}
|
|
39
47
|
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
let _pipelinesData = [];
|
|
4
4
|
let _pipelinePollId = null;
|
|
5
5
|
let _pipelinePollInterval = null;
|
|
6
|
+
const PIPELINE_PER_PAGE = 25;
|
|
7
|
+
let _pipelinePage = 0;
|
|
8
|
+
let _pipelineTotalPages = 1;
|
|
6
9
|
function _stopPipelinePoll() { if (_pipelinePollInterval) { clearInterval(_pipelinePollInterval); _pipelinePollInterval = null; } _pipelinePollId = null; }
|
|
7
10
|
|
|
8
11
|
/**
|
|
@@ -293,11 +296,18 @@ function renderPipelines(pipelines) {
|
|
|
293
296
|
if (!pipelines || pipelines.length === 0) {
|
|
294
297
|
countEl.textContent = '0';
|
|
295
298
|
el.innerHTML = '<p class="empty">No pipelines yet. Create one to chain stages like audit \u2192 meeting \u2192 plan \u2192 merge.</p>';
|
|
299
|
+
_pipelineTotalPages = 1;
|
|
296
300
|
return;
|
|
297
301
|
}
|
|
298
302
|
countEl.textContent = pipelines.length;
|
|
299
303
|
|
|
300
|
-
|
|
304
|
+
const totalPages = Math.ceil(pipelines.length / PIPELINE_PER_PAGE);
|
|
305
|
+
_pipelineTotalPages = totalPages;
|
|
306
|
+
if (_pipelinePage >= totalPages) _pipelinePage = Math.max(0, totalPages - 1);
|
|
307
|
+
const start = _pipelinePage * PIPELINE_PER_PAGE;
|
|
308
|
+
const pageItems = pipelines.slice(start, start + PIPELINE_PER_PAGE);
|
|
309
|
+
|
|
310
|
+
el.innerHTML = pageItems.map(function(p) {
|
|
301
311
|
const activeRun = _getPipelineActiveRun(p);
|
|
302
312
|
const lastRun = (p.runs || []).slice(-1)[0];
|
|
303
313
|
const statusColor = activeRun ? 'var(--blue)' : lastRun?.status === 'completed' ? 'var(--green)' : lastRun?.status === 'failed' ? 'var(--red)' : lastRun?.status === 'stopped' ? 'var(--yellow)' : 'var(--muted)';
|
|
@@ -336,6 +346,29 @@ function renderPipelines(pipelines) {
|
|
|
336
346
|
progressHtml +
|
|
337
347
|
'</div>';
|
|
338
348
|
}).join('');
|
|
349
|
+
|
|
350
|
+
if (pipelines.length > PIPELINE_PER_PAGE) {
|
|
351
|
+
el.insertAdjacentHTML('beforeend', '<div class="pr-pager">' +
|
|
352
|
+
'<span class="pr-page-info">Showing ' + (start + 1) + ' to ' + Math.min(start + PIPELINE_PER_PAGE, pipelines.length) + ' of ' + pipelines.length + '</span>' +
|
|
353
|
+
'<div class="pr-pager-btns">' +
|
|
354
|
+
'<button class="pr-pager-btn ' + (_pipelinePage === 0 ? 'disabled' : '') + '" onclick="_pipelinePrev()">Prev</button>' +
|
|
355
|
+
'<button class="pr-pager-btn ' + (_pipelinePage >= totalPages - 1 ? 'disabled' : '') + '" onclick="_pipelineNext()">Next</button>' +
|
|
356
|
+
'</div></div>');
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function _pipelinePrev() {
|
|
361
|
+
if (_pipelinePage > 0) {
|
|
362
|
+
_pipelinePage--;
|
|
363
|
+
renderPipelines(_pipelinesData);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function _pipelineNext() {
|
|
368
|
+
if (_pipelinePage < _pipelineTotalPages - 1) {
|
|
369
|
+
_pipelinePage++;
|
|
370
|
+
renderPipelines(_pipelinesData);
|
|
371
|
+
}
|
|
339
372
|
}
|
|
340
373
|
|
|
341
374
|
function openPipelineDetail(id) {
|
|
@@ -56,8 +56,16 @@ async function _submitCreatePlan(e) {
|
|
|
56
56
|
async function refreshPlans() {
|
|
57
57
|
try {
|
|
58
58
|
const plans = await fetch('/api/plans').then(r => r.json());
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
// Cross-slice readers (renderMeetings, plan modal, command-center, etc.)
|
|
60
|
+
// depend on _lastPlans staying current — assign BEFORE the change gate so
|
|
61
|
+
// they see fresh data even when the renderer is skipped (F2/F3).
|
|
62
|
+
window._lastPlans = plans;
|
|
63
|
+
// Skip the full plans-list innerHTML rewrite when /api/plans returned the
|
|
64
|
+
// same payload as last tick. Dedicated cache key 'plansPayload' avoids
|
|
65
|
+
// colliding with the 'plans' slice tracked by _processStatusUpdate (F6).
|
|
66
|
+
if (typeof _changed !== 'function' || _changed('plansPayload', plans)) {
|
|
67
|
+
renderPlans(plans);
|
|
68
|
+
}
|
|
61
69
|
} catch (e) { console.error('plans refresh:', e.message); }
|
|
62
70
|
}
|
|
63
71
|
|
package/dashboard/js/state.js
CHANGED
|
@@ -25,11 +25,47 @@ function getPageFromUrl() {
|
|
|
25
25
|
|
|
26
26
|
let currentPage = getPageFromUrl();
|
|
27
27
|
|
|
28
|
+
// W-mpgb0xa7000d90d4 (dashboard-refresh-audit.md F5) — switchPage previously
|
|
29
|
+
// only cleaned intervals and flipped CSS; tabs like /plans and /inbox waited
|
|
30
|
+
// up to ~12s for refresh.js's slow-cycle (refreshKnowledgeBase + refreshPlans
|
|
31
|
+
// every 3rd status tick) before showing fresh data. The QA tab worked around
|
|
32
|
+
// this with its own switchPage monkey-patch in qa.js (__qaWrapped) — that
|
|
33
|
+
// per-tab pattern is now generalized into one canonical lifecycle below.
|
|
34
|
+
//
|
|
35
|
+
// PAGE_LAZY_LOADERS: functions to invoke when ENTERING a page (resolved by
|
|
36
|
+
// name through window so the function body can live alongside its tab UI).
|
|
37
|
+
// PAGE_LEAVE_HOOKS: functions to invoke on EVERY switchPage call regardless
|
|
38
|
+
// of source/destination. Each leave hook must be safe to invoke when there
|
|
39
|
+
// is nothing to clean up (idempotent / no-op when inactive) — _stopPlanPoll,
|
|
40
|
+
// _stopMeetingPoll, _stopQaRunsPoll all clearInterval on a nullable handle;
|
|
41
|
+
// closeDetail / closeManagedLog short-circuit when no panel/stream is open.
|
|
42
|
+
const PAGE_LAZY_LOADERS = {
|
|
43
|
+
qa: ['loadQaTargets', 'loadQaRunbooks', '_startQaRunsPoll'],
|
|
44
|
+
plans: ['refreshPlans'],
|
|
45
|
+
inbox: ['refreshKnowledgeBase'],
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const PAGE_LEAVE_HOOKS = [
|
|
49
|
+
'_stopPlanPoll',
|
|
50
|
+
'_stopMeetingPoll',
|
|
51
|
+
'closeDetail',
|
|
52
|
+
'_stopQaRunsPoll',
|
|
53
|
+
'closeManagedLog',
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
function _invokePageHooks(names) {
|
|
57
|
+
if (!names || !names.length) return;
|
|
58
|
+
const scope = (typeof window !== 'undefined') ? window
|
|
59
|
+
: (typeof globalThis !== 'undefined' ? globalThis : {});
|
|
60
|
+
for (const name of names) {
|
|
61
|
+
const fn = scope[name];
|
|
62
|
+
if (typeof fn !== 'function') continue;
|
|
63
|
+
try { fn(); } catch { /* per-hook isolation — one bad hook can't break the lifecycle */ }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
28
67
|
function switchPage(page, pushState) {
|
|
29
|
-
|
|
30
|
-
try { _stopPlanPoll(); } catch {}
|
|
31
|
-
try { _stopMeetingPoll(); } catch {}
|
|
32
|
-
try { closeDetail(); } catch {}
|
|
68
|
+
_invokePageHooks(PAGE_LEAVE_HOOKS);
|
|
33
69
|
|
|
34
70
|
currentPage = page;
|
|
35
71
|
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
|
|
@@ -46,6 +82,8 @@ function switchPage(page, pushState) {
|
|
|
46
82
|
const url = page === 'home' ? '/' : '/' + page;
|
|
47
83
|
history.pushState({ page }, '', url);
|
|
48
84
|
}
|
|
85
|
+
|
|
86
|
+
_invokePageHooks(PAGE_LAZY_LOADERS[page]);
|
|
49
87
|
}
|
|
50
88
|
|
|
51
89
|
// Browser back/forward navigation
|
|
@@ -160,4 +198,4 @@ function safeFetch(url, opts) {
|
|
|
160
198
|
return fetch(url, fetchOpts).finally(function() { clearTimeout(timer); });
|
|
161
199
|
}
|
|
162
200
|
|
|
163
|
-
window.MinionsState = { getPageFromUrl, switchPage, getPrdRequeueState, setPrdRequeueState, clearPrdRequeueState, prunePrdRequeueState, rerenderPrdFromCache, safeFetch };
|
|
201
|
+
window.MinionsState = { getPageFromUrl, switchPage, getPrdRequeueState, setPrdRequeueState, clearPrdRequeueState, prunePrdRequeueState, rerenderPrdFromCache, safeFetch, PAGE_LAZY_LOADERS, PAGE_LEAVE_HOOKS };
|
package/dashboard.js
CHANGED
|
@@ -1152,6 +1152,10 @@ function rebuildDashboardHtml() {
|
|
|
1152
1152
|
HTML = HTML_RAW;
|
|
1153
1153
|
HTML_GZ = zlib.gzipSync(HTML);
|
|
1154
1154
|
HTML_ETAG = '"' + require('crypto').createHash('md5').update(HTML).digest('hex') + '"';
|
|
1155
|
+
// Bust the /api/status cache so the new dashboardBuildId propagates on the
|
|
1156
|
+
// next poll — refresh.js compares it against its first-observed value and
|
|
1157
|
+
// hard-reloads on mismatch (R3, W-mpgb0xgc000hf1d3).
|
|
1158
|
+
try { invalidateStatusCache(); } catch { /* status cache may not be initialized yet */ }
|
|
1155
1159
|
console.log(' Dashboard hot-reloaded');
|
|
1156
1160
|
// Push reload to all connected browsers via status-stream (saves a connection)
|
|
1157
1161
|
for (const res of _statusStreamClients) {
|
|
@@ -1684,6 +1688,12 @@ function _buildStatusSlowState() {
|
|
|
1684
1688
|
dashboardRunning: _dashboardVersion.codeVersion,
|
|
1685
1689
|
dashboardRunningCommit: _dashboardVersion.codeCommit,
|
|
1686
1690
|
dashboardStartedAt: _dashboardVersion.startedAt,
|
|
1691
|
+
// dashboardBuildId — md5 of the assembled HTML (built JS + pages + css).
|
|
1692
|
+
// Refresh.js compares this against its first-observed value and hard-
|
|
1693
|
+
// reloads on mismatch so renderer hot-edits across restarts always land
|
|
1694
|
+
// on the client (R3, W-mpgb0xgc000hf1d3). Quotes stripped from the
|
|
1695
|
+
// weak/strong ETag form so consumers see a bare hex digest.
|
|
1696
|
+
dashboardBuildId: HTML_ETAG ? HTML_ETAG.replace(/^"|"$/g, '') : null,
|
|
1687
1697
|
disk: diskVersion,
|
|
1688
1698
|
diskCommit,
|
|
1689
1699
|
engineStale,
|
|
@@ -9306,6 +9316,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
9306
9316
|
engineRunningCommit: engine.codeCommit || null,
|
|
9307
9317
|
dashboardRunning: _dashboardVersion.codeVersion,
|
|
9308
9318
|
dashboardRunningCommit: _dashboardVersion.codeCommit,
|
|
9319
|
+
dashboardBuildId: HTML_ETAG ? HTML_ETAG.replace(/^"|"$/g, '') : null,
|
|
9309
9320
|
latest: npm.latest,
|
|
9310
9321
|
updateAvailable: !isGitRepo && !!(diskVersion && npm.latest && _compareVersions(npm.latest, diskVersion) > 0),
|
|
9311
9322
|
engineStale,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2017",
|
|
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"
|