@yemi33/minions 0.1.2014 → 0.1.2016

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.
@@ -74,6 +74,17 @@ function _processStatusUpdate(data) {
74
74
  const threshEl = document.getElementById('inbox-threshold');
75
75
  if (threshEl && data.autoMode?.inboxThreshold) threshEl.textContent = data.autoMode.inboxThreshold;
76
76
 
77
+ // Publish window._last* snapshots BEFORE any renderer runs. Several renderers
78
+ // (renderProjects, renderPrd, renderPrs, refreshPlans, and others) read
79
+ // window._lastStatus / window._lastWorkItems / window._lastDispatch during
80
+ // their render path. If we assign these after the renderer calls, every tick
81
+ // consumes the PREVIOUS tick's globals — a one-tick (~4 s) staleness lag
82
+ // baked into the polling loop (W-mpgb0x81000cf8df, audit F4). No renderer in
83
+ // this function mutates `data`, so hoisting is safe.
84
+ window._lastDispatch = data.dispatch;
85
+ window._lastWorkItems = data.workItems || [];
86
+ window._lastStatus = data;
87
+
77
88
  // Render only changed sections
78
89
  if (_changed('agents', data.agents)) { renderAgents(data.agents); cmdUpdateAgentList(data.agents); }
79
90
  if (_changed('prdProgress', data.prdProgress) || _changed('prdPrs', data.pullRequests?.length)) { renderPrdProgress(data.prdProgress); _cachePrdItems(data.prdProgress); }
@@ -81,13 +92,19 @@ function _processStatusUpdate(data) {
81
92
  if (_changed('projects', data.projects)) { cmdUpdateProjectList(data.projects || []); renderProjects(data.projects || []); }
82
93
  // FRE banner — safe to call every tick (idempotent + cheap). Pass the full
83
94
  // status payload so the runtime-CLI explainer reads autoMode.defaultCli from
84
- // THIS tick rather than the previous one (window._lastStatus is set later).
95
+ // THIS tick (window._lastStatus is hoisted above, but renderFre takes the
96
+ // payload directly to avoid the window-global indirection).
85
97
  if (typeof renderFre === 'function') {
86
98
  try { renderFre(data); } catch { /* expected on first load */ }
87
99
  }
88
100
  if (_changed('notes', data.notes)) renderNotes(data.notes);
89
101
  if (_changed('prd', [data.prd, data.prdProgress])) renderPrd(data.prd, data.prdProgress);
90
- if (_changed('prs', data.pullRequests)) renderPrs(data.pullRequests || []);
102
+ // Capture prs + workItems change signals once — also reused by the cross-slice
103
+ // render triggers at the bottom of this function (F1/F3, W-mpgb0xbh000e3b86).
104
+ // _changed mutates _sectionCache so it must be called exactly once per key.
105
+ var _prsChanged = _changed('prs', data.pullRequests);
106
+ var _workItemsChanged = _changed('workItems', data.workItems);
107
+ if (_prsChanged) renderPrs(data.pullRequests || []);
91
108
  if (_changed('archivedPrds', data.archivedPrds)) renderArchiveButtons(data.archivedPrds || []);
92
109
  if (_changed('engine', data.engine)) {
93
110
  renderEngineStatus(data.engine);
@@ -103,9 +120,6 @@ function _processStatusUpdate(data) {
103
120
  if (_changed('adoThrottle', data.adoThrottle)) renderAdoThrottleAlert(data.adoThrottle);
104
121
  if (_changed('ghThrottle', data.ghThrottle)) renderGhThrottleAlert(data.ghThrottle);
105
122
  if (_changed('dispatch', data.dispatch)) renderDispatch(data.dispatch);
106
- window._lastDispatch = data.dispatch;
107
- window._lastWorkItems = data.workItems || [];
108
- window._lastStatus = data;
109
123
  prunePrdRequeueState(window._lastWorkItems);
110
124
  if (_changed('engineLog', data.engineLog)) renderEngineLog(data.engineLog || []);
111
125
  if (_changed('metrics', data.metrics)) renderMetrics(data.metrics || {});
@@ -127,7 +141,7 @@ function _processStatusUpdate(data) {
127
141
  .catch(function () { /* keep render even if managed fetch failed — getLastItems() returns the last good cache (or []) */ })
128
142
  .then(function () { try { renderKeepProcesses(); } catch {} });
129
143
  }
130
- if (_changed('workItems', data.workItems)) renderWorkItems(data.workItems || []);
144
+ if (_workItemsChanged) renderWorkItems(data.workItems || []);
131
145
  if (_changed('skills', data.skills)) renderSkills(data.skills || []);
132
146
  if (_changed('mcpServers', data.mcpServers)) renderMcpServers(data.mcpServers || []);
133
147
  if (_changed('schedules', data.schedules)) renderSchedules(data.schedules || []);
@@ -144,6 +158,27 @@ function _processStatusUpdate(data) {
144
158
  if (!window._kbRefreshCount) window._kbRefreshCount = 0;
145
159
  if (window._kbRefreshCount++ % 3 === 0) { refreshKnowledgeBase(); refreshPlans(); }
146
160
 
161
+ // Cross-slice render triggers (F1/F3, W-mpgb0xbh000e3b86): renderPrs reads
162
+ // window._lastWorkItems for the +N follow-up chip count and derivePlanStatus
163
+ // reads window._lastStatus.pullRequests for verify-follow-up reconciliation.
164
+ // When workItems OR pullRequests change, re-render the dependents against the
165
+ // freshest cached data — without waiting up to 12s for the next refreshPlans
166
+ // cycle (F3) or for a PR object to mutate (F1). Runs after the window._last*
167
+ // assignments above so the cached globals these renderers consult are fresh.
168
+ if (_workItemsChanged && !_prsChanged) {
169
+ // F1: only the work-item slice moved this tick — renderPrs wasn't called
170
+ // above, so the +N follow-up chip would otherwise stay stale until the
171
+ // next PR mutation.
172
+ renderPrs(data.pullRequests || []);
173
+ }
174
+ if ((_workItemsChanged || _prsChanged) && Array.isArray(window._lastPlans) && typeof renderPlans === 'function') {
175
+ // F3: derivePlanStatus + _renderVerifyBadge derive from pullRequests +
176
+ // workItems. Re-render against the cached plan list so plan status flips
177
+ // within one /api/status tick (~4s) instead of one refreshPlans poll
178
+ // (~12s). No-op when _lastPlans hasn't been populated yet (first load).
179
+ renderPlans(window._lastPlans);
180
+ }
181
+
147
182
  // Sidebar activity indicators — show red dot on pages with new activity
148
183
  try {
149
184
  const changes = _detectPageChanges(data);
@@ -164,7 +199,15 @@ let _knownDashboardStartId = null;
164
199
  // downstream consumers of `data`.
165
200
  let _lastStatusEtag = null;
166
201
  let _lastStatusData = null;
202
+ // In-flight gate (S5 — dashboard-refresh-audit). setInterval(refresh, 4000)
203
+ // can interleave two ticks inside _processStatusUpdate if a refresh takes
204
+ // >4 s (cold server cache + slow JSON + network blip). Skip the second tick
205
+ // when the first hasn't finished — the next interval fire picks up the
206
+ // fresh state anyway.
207
+ let _refreshInFlight = false;
167
208
  async function refresh() {
209
+ if (_refreshInFlight) return;
210
+ _refreshInFlight = true;
168
211
  try {
169
212
  const headers = {};
170
213
  if (_lastStatusEtag) headers['If-None-Match'] = _lastStatusEtag;
@@ -191,6 +234,7 @@ async function refresh() {
191
234
  if (dashId) _knownDashboardStartId = dashId;
192
235
  _processStatusUpdate(data);
193
236
  } catch(e) { console.error('refresh error', e); }
237
+ finally { _refreshInFlight = false; }
194
238
  }
195
239
 
196
240
  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
- renderKnowledgeBase();
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
 
@@ -1,7 +1,7 @@
1
1
  // render-meetings.js — Team meeting rendering
2
2
 
3
3
  let _showArchived = false;
4
- const MTG_PER_PAGE = 10;
4
+ const MTG_PER_PAGE = 25;
5
5
  let _mtgPage = 0;
6
6
  let _lastMeetingHash = '';
7
7
  let _lastMeetingsForPaging = [];
@@ -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
- el.innerHTML = pipelines.map(function(p) {
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
- window._lastPlans = plans; // Store globally so plan modal can access
60
- renderPlans(plans);
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2014",
3
+ "version": "0.1.2016",
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"