@yemi33/minions 0.1.2059 → 0.1.2061
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,21 +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
|
-
// workItems is exempt from the _changed gate, mirroring the agents fix
|
|
378
|
-
// (W-mpn7keq9000302c9). Real-time correctness on /work — a CC-dispatched
|
|
379
|
-
// work item should appear within one poll, not "after a hard refresh" —
|
|
380
|
-
// beats the cost of re-rendering one 20-row table per 4s tick. The
|
|
381
|
-
// _workItemsChanged flag is still captured above so the F1/F3 cross-slice
|
|
382
|
-
// triggers below (renderPrs + renderPlans) still gate correctly, and the
|
|
383
|
-
// diag ring-buffer keeps seeing the per-tick changed signal.
|
|
384
403
|
renderWorkItems(data.workItems || []);
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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 || []);
|
|
392
418
|
// Sidebar counts (cheap)
|
|
393
419
|
const swi = document.getElementById('sidebar-wi');
|
|
394
420
|
if (swi) swi.textContent = (data.workItems || []).length || '';
|
|
@@ -69,24 +69,18 @@ function renderAgents(agents) {
|
|
|
69
69
|
`).join('');
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
// Emoji, name and role are all user-controlled fields; routing them through textContent
|
|
80
|
-
// guarantees no HTML interpretation even if the escape function were ever bypassed.
|
|
72
|
+
// Re-render the detail-panel header (name + emoji + runtime/model chips +
|
|
73
|
+
// status badge + lastAction + blocking-tool / resultSummary blocks) from a
|
|
74
|
+
// slim agentData record. Extracted so renderAgents can call it every poll
|
|
75
|
+
// tick to keep an open detail panel live without re-fetching the expensive
|
|
76
|
+
// /api/agent/<id> charter/history/output payload. SEC-03 Phase A rules
|
|
77
|
+
// still apply: emoji/name/role go through textContent.
|
|
78
|
+
function _renderAgentDetailHeader(agent) {
|
|
81
79
|
const nameEl = document.getElementById('detail-agent-name');
|
|
80
|
+
if (!nameEl) return;
|
|
82
81
|
const emojiSpan = document.createElement('span');
|
|
83
82
|
emojiSpan.style.fontSize = '22px';
|
|
84
83
|
emojiSpan.textContent = agent.emoji || '';
|
|
85
|
-
// Runtime tag \u2014 uses the inline-SVG logo from the same RUNTIME_TAGS map the
|
|
86
|
-
// card uses, so the visual is consistent. The container's user-controlled
|
|
87
|
-
// text fields stay on the textContent path; the SVG is a hardcoded literal
|
|
88
|
-
// from RUNTIME_TAGS keyed by the runtime string (server-controlled, finite
|
|
89
|
-
// set), so injecting via innerHTML on the icon-only span is safe.
|
|
90
84
|
const runtimeMeta = RUNTIME_TAGS[agent.runtime];
|
|
91
85
|
const runtimeSpan = document.createElement('span');
|
|
92
86
|
runtimeSpan.title = 'Runtime: ' + (runtimeMeta?.label || agent.runtime || 'unknown');
|
|
@@ -100,9 +94,6 @@ async function openAgentDetail(id) {
|
|
|
100
94
|
runtimeSpan.style.cssText += ';font-size:10px;font-weight:600;letter-spacing:0.4px;text-transform:uppercase;padding:2px 6px;border:1px solid var(--muted);border-radius:3px;color:var(--muted)';
|
|
101
95
|
runtimeSpan.textContent = agent.runtime || 'unknown';
|
|
102
96
|
}
|
|
103
|
-
// W-mpmwxk4y00053271 — mirror the model chip the card shows so the detail
|
|
104
|
-
// header stays in sync. textContent path keeps the model string from being
|
|
105
|
-
// interpreted as HTML.
|
|
106
97
|
const modelSpan = (agent.model && typeof agent.model === 'string')
|
|
107
98
|
? (() => {
|
|
108
99
|
const s = document.createElement('span');
|
|
@@ -121,12 +112,27 @@ async function openAgentDetail(id) {
|
|
|
121
112
|
nameEl.replaceChildren(...children);
|
|
122
113
|
|
|
123
114
|
const badgeClass = agent.status;
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
115
|
+
const statusEl = document.getElementById('detail-status-line');
|
|
116
|
+
if (statusEl) {
|
|
117
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; status is an internal bounded enum and all user data is wrapped in escapeHtml()/renderMd() (fields: lastAction, blocking tool, resultSummary)
|
|
118
|
+
statusEl.innerHTML =
|
|
119
|
+
'<span class="status-badge ' + badgeClass + '">' + agent.status.toUpperCase() + '</span> ' +
|
|
120
|
+
'<span style="color:var(--muted)">' + escapeHtml(agent.lastAction) + '</span>' +
|
|
121
|
+
(agent._blockingToolCall ? '<div style="margin-top:4px;padding:4px 8px;background:rgba(130,160,210,0.13);border:1px solid rgba(130,160,210,0.3);border-radius:4px;font-size:11px;color:var(--muted)">⏳ Blocking tool call (' + escapeHtml(agent._blockingToolCall.tool) + ') — silent ' + Math.round(agent._blockingToolCall.silentMs/60000) + 'min, timeout in ' + Math.round(agent._blockingToolCall.remainingMs/60000) + 'min</div>' : '') +
|
|
122
|
+
(agent.resultSummary ? '<div style="margin-top:4px;font-size:11px;color:var(--text);line-height:1.4">' + renderMd(agent.resultSummary.slice(0, 300)) + '</div>' : '');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function openAgentDetail(id) {
|
|
127
|
+
const agent = agentData.find(a => a.id === id);
|
|
128
|
+
if (!agent) return;
|
|
129
|
+
currentAgentId = id;
|
|
130
|
+
currentTab = (agent.status === 'working') ? 'live' : 'thought-process';
|
|
131
|
+
|
|
132
|
+
// SEC-03 Phase A: Build the detail header via DOM + textContent instead of innerHTML.
|
|
133
|
+
// Emoji, name and role are all user-controlled fields; routing them through textContent
|
|
134
|
+
// guarantees no HTML interpretation even if the escape function were ever bypassed.
|
|
135
|
+
_renderAgentDetailHeader(agent);
|
|
130
136
|
|
|
131
137
|
// Show panel immediately with loading state — don't wait for API
|
|
132
138
|
document.getElementById('detail-content').innerHTML = '<div style="padding:24px;text-align:center;color:var(--muted)">Loading...</div>';
|
|
@@ -161,7 +167,12 @@ function _tickAgentRuntimes() {
|
|
|
161
167
|
el.textContent = 'Running: ' + (hr > 0 ? hr + 'h ' : '') + min + 'm ' + sec + 's';
|
|
162
168
|
});
|
|
163
169
|
}
|
|
164
|
-
// Start ticker after each render if working agents exist
|
|
170
|
+
// Start ticker after each render if working agents exist, and refresh the
|
|
171
|
+
// open agent-detail panel header so status / lastAction / blocking-tool /
|
|
172
|
+
// resultSummary track the latest /api/status slice without re-fetching the
|
|
173
|
+
// expensive /api/agent/<id> charter/history/output payload. The body tabs
|
|
174
|
+
// (thought-process, live, output, etc.) are loaded once on open via
|
|
175
|
+
// openAgentDetail's safeFetch — they stay as-is until the user reopens.
|
|
165
176
|
var _origRenderAgents = renderAgents;
|
|
166
177
|
renderAgents = function(agents) {
|
|
167
178
|
_origRenderAgents(agents);
|
|
@@ -170,6 +181,10 @@ renderAgents = function(agents) {
|
|
|
170
181
|
_tickAgentRuntimes();
|
|
171
182
|
_agentRuntimeTimer = setInterval(_tickAgentRuntimes, 1000);
|
|
172
183
|
}
|
|
184
|
+
if (currentAgentId) {
|
|
185
|
+
var open = agents.find(function(a) { return a.id === currentAgentId; });
|
|
186
|
+
if (open) _renderAgentDetailHeader(open);
|
|
187
|
+
}
|
|
173
188
|
};
|
|
174
189
|
|
|
175
190
|
window.MinionsAgents = { renderAgents, openAgentDetail };
|
|
@@ -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.2061",
|
|
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"
|