@yemi33/minions 0.1.2030 → 0.1.2032
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/refresh.js +49 -14
- package/dashboard/js/render-pipelines.js +20 -2
- package/package.json +1 -1
package/dashboard/js/refresh.js
CHANGED
|
@@ -36,13 +36,21 @@ function _detectPageChanges(data) {
|
|
|
36
36
|
|
|
37
37
|
// Change detection — skip renders for sections that haven't changed since last refresh.
|
|
38
38
|
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
39
|
+
// Two-layer cache:
|
|
40
|
+
// 1. `_lastValueByKey` is an O(1) reference-equality precheck: when the server
|
|
41
|
+
// returns 304 Not Modified (refresh.js ~173) we reuse the same `_lastStatusData`
|
|
42
|
+
// reference tick after tick, so every `_changed(key, data.<slice>)` call below
|
|
43
|
+
// would otherwise re-stringify multi-MB slices for nothing. The JSON.stringify
|
|
44
|
+
// path stays as the fallback for the "fresh object, equal contents" case
|
|
45
|
+
// (S3, W-mpgcikfg000g8dc7).
|
|
46
|
+
// 2. RENDER_VERSIONS is the in-process cache-bust knob (R3, W-mpgb0xgc000hf1d3).
|
|
47
|
+
// _changed() caches JSON.stringify(value) per (key, version), so when the
|
|
48
|
+
// input data is byte-identical between ticks the render is skipped. That's
|
|
49
|
+
// the right call 99% of the time, but it has a sharp edge: when a renderer
|
|
50
|
+
// body itself is edited (hot-reloaded via dashboard-build.js or a freshly
|
|
51
|
+
// shipped bundle) and the input stays the same, the stale render persists
|
|
52
|
+
// forever because the cache key still matches. F8 / "projects chip" class
|
|
53
|
+
// of bug.
|
|
46
54
|
//
|
|
47
55
|
// Bump the matching entry below whenever a renderer's *output* may change
|
|
48
56
|
// for the same input. Cross-restart safety lives in the dashboardBuildId
|
|
@@ -74,15 +82,24 @@ const RENDER_VERSIONS = {
|
|
|
74
82
|
pinned: 1,
|
|
75
83
|
};
|
|
76
84
|
const _sectionCache = {};
|
|
85
|
+
const _lastValueByKey = {};
|
|
77
86
|
const _sectionCacheVersions = {};
|
|
78
87
|
function _changed(key, value, version) {
|
|
79
88
|
var v = version == null ? (RENDER_VERSIONS[key] || 0) : version;
|
|
80
89
|
// Drop the stale-version entry so the cache doesn't grow unbounded across bumps.
|
|
90
|
+
// A version bump must also reset the ref-eq sentinel — otherwise the S3
|
|
91
|
+
// short-circuit below would defeat the R3 cache-bust contract when the
|
|
92
|
+
// caller passes the SAME object reference across a version bump.
|
|
81
93
|
if (_sectionCacheVersions[key] !== undefined && _sectionCacheVersions[key] !== v) {
|
|
82
94
|
delete _sectionCache[key + ':v' + _sectionCacheVersions[key]];
|
|
95
|
+
_lastValueByKey[key] = undefined;
|
|
83
96
|
}
|
|
84
97
|
_sectionCacheVersions[key] = v;
|
|
85
98
|
var cacheKey = key + ':v' + v;
|
|
99
|
+
// Reference-equality short-circuit: skip the stringify entirely when the
|
|
100
|
+
// server returned 304 and the same object reference is being re-checked.
|
|
101
|
+
if (_lastValueByKey[key] === value) return false;
|
|
102
|
+
_lastValueByKey[key] = value;
|
|
86
103
|
var json = JSON.stringify(value);
|
|
87
104
|
if (_sectionCache[cacheKey] === json) return false;
|
|
88
105
|
_sectionCache[cacheKey] = json;
|
|
@@ -128,17 +145,35 @@ function _processStatusUpdate(data) {
|
|
|
128
145
|
const threshEl = document.getElementById('inbox-threshold');
|
|
129
146
|
if (threshEl && data.autoMode?.inboxThreshold) threshEl.textContent = data.autoMode.inboxThreshold;
|
|
130
147
|
|
|
131
|
-
// Publish window._last* snapshots BEFORE any renderer runs. Several
|
|
132
|
-
// (
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
148
|
+
// Publish window._last* snapshots BEFORE any renderer runs. Several
|
|
149
|
+
// renderers (renderPrd, renderPrs, derivePlanStatus inside refreshPlans,
|
|
150
|
+
// and the projectChipRemove/optimisticallyAddProject paths off
|
|
151
|
+
// renderProjects) read window._lastStatus / window._lastWorkItems /
|
|
152
|
+
// window._lastDispatch synchronously during their render path. When the
|
|
153
|
+
// assignments lived AFTER the renderer calls (the pre-fix layout in
|
|
154
|
+
// refresh.js:106-108), every tick consumed the PREVIOUS tick's globals —
|
|
155
|
+
// a one-tick (~4 s) staleness lag baked into the polling loop on top of
|
|
156
|
+
// the natural poll interval.
|
|
157
|
+
//
|
|
158
|
+
// See dashboard-refresh-audit.md finding F4 (W-mpgb0x81000cf8df / PR #2753
|
|
159
|
+
// landed the initial hoist; W-mpgbzpn9000390ae extends it with the
|
|
160
|
+
// render-agents / render-work-items invariants below). No renderer below
|
|
161
|
+
// mutates `data`, so hoisting the publish is safe; the renderers
|
|
162
|
+
// themselves still take their slice as a direct argument.
|
|
163
|
+
//
|
|
164
|
+
// Locking-in note (W-mpgbzpn9000390ae): renderAgents
|
|
165
|
+
// (render-agents.js:38) and renderWorkItems (render-work-items.js:98) —
|
|
166
|
+
// the two surfaces the user explicitly called out on /home and /work —
|
|
167
|
+
// take their data slice via argument and DO NOT read window._last*.
|
|
168
|
+
// Hoisting the publish here additionally guarantees that any future
|
|
169
|
+
// refactor that adds a window._last* read to those renderers will see
|
|
170
|
+
// fresh data on the same tick, not the previous one. Covered by
|
|
171
|
+
// dashboard-resilience.test.js source-inspection assertions.
|
|
138
172
|
window._lastDispatch = data.dispatch;
|
|
139
173
|
window._lastWorkItems = data.workItems || [];
|
|
140
174
|
window._lastStatus = data;
|
|
141
175
|
|
|
176
|
+
|
|
142
177
|
// Render only changed sections
|
|
143
178
|
if (_changed('agents', data.agents)) { renderAgents(data.agents); cmdUpdateAgentList(data.agents); }
|
|
144
179
|
if (_changed('prdProgress', data.prdProgress) || _changed('prdPrs', data.pullRequests?.length)) { renderPrdProgress(data.prdProgress); _cachePrdItems(data.prdProgress); }
|
|
@@ -491,7 +491,7 @@ function openPipelineDetail(id) {
|
|
|
491
491
|
var fresh = list.find(function(x) { return x.id === id; });
|
|
492
492
|
if (fresh) {
|
|
493
493
|
// Only re-render if data changed
|
|
494
|
-
var newHash =
|
|
494
|
+
var newHash = _computePipelineDetailHash(fresh);
|
|
495
495
|
if (newHash !== _pipelinePollHash) {
|
|
496
496
|
_pipelinePollHash = newHash;
|
|
497
497
|
_pipelinesData = _pipelinesData.map(function(x) { return x.id === id ? fresh : x; });
|
|
@@ -504,6 +504,24 @@ function openPipelineDetail(id) {
|
|
|
504
504
|
}
|
|
505
505
|
var _pipelinePollHash = '';
|
|
506
506
|
|
|
507
|
+
// F10: hash all pipeline fields the detail modal renders so stages/cron/monitoredResources/stopWhen
|
|
508
|
+
// edits in another tab trigger re-render. Previously only {runs, enabled, _stoppedBy, _stopReason}.
|
|
509
|
+
function _computePipelineDetailHash(p) {
|
|
510
|
+
if (!p) return '';
|
|
511
|
+
return JSON.stringify({
|
|
512
|
+
runs: p.runs || [],
|
|
513
|
+
enabled: p.enabled,
|
|
514
|
+
_stoppedBy: p._stoppedBy,
|
|
515
|
+
_stopReason: p._stopReason,
|
|
516
|
+
stages: p.stages,
|
|
517
|
+
monitoredResources: p.monitoredResources,
|
|
518
|
+
stopWhen: p.stopWhen,
|
|
519
|
+
trigger: p.trigger,
|
|
520
|
+
name: p.name,
|
|
521
|
+
description: p.description
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
|
|
507
525
|
/**
|
|
508
526
|
* Fetch fresh pipeline data and re-render the detail modal immediately.
|
|
509
527
|
* Used after actions (continue, trigger, abort) to avoid waiting for the 4s poll.
|
|
@@ -516,7 +534,7 @@ async function _refreshPipelineDetail(id) {
|
|
|
516
534
|
var fresh = list.find(function(x) { return x.id === id; });
|
|
517
535
|
if (fresh) {
|
|
518
536
|
_pipelinesData = _pipelinesData.map(function(x) { return x.id === id ? fresh : x; });
|
|
519
|
-
_pipelinePollHash =
|
|
537
|
+
_pipelinePollHash = _computePipelineDetailHash(fresh);
|
|
520
538
|
renderPipelines(_pipelinesData);
|
|
521
539
|
openPipelineDetail(id);
|
|
522
540
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2032",
|
|
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"
|