@yemi33/minions 0.1.2287 → 0.1.2289
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/render-plans.js +7 -2
- package/dashboard/js/settings.js +3 -0
- package/dashboard/slim/body.html +4 -11
- package/dashboard/slim/js/modals-tiles.js +66 -1
- package/dashboard/slim/js/status.js +82 -98
- package/dashboard/slim/styles.css +13 -87
- package/dashboard.js +81 -50
- package/docs/deprecated.json +175 -37
- package/docs/live-checkout-mode.md +26 -4
- package/engine/ado.js +11 -2
- package/engine/github.js +7 -0
- package/engine/lifecycle.js +9 -3
- package/engine/live-checkout.js +139 -8
- package/engine/playbook.js +5 -0
- package/engine/prd-store.js +1 -1
- package/engine/queries.js +10 -3
- package/engine/shared.js +104 -5
- package/engine.js +143 -9
- package/package.json +1 -1
- package/playbooks/plan-to-prd.md +2 -1
- package/playbooks/shared-rules.md +1 -0
|
@@ -189,8 +189,9 @@ function derivePlanStatus(prdFile, mdFile, prdJsonStatus, workItems, opts) {
|
|
|
189
189
|
if (prdJsonStatus === 'rejected') return 'rejected';
|
|
190
190
|
if (prdJsonStatus === 'paused' && !allDone) return 'paused';
|
|
191
191
|
if (prdJsonStatus === 'revision-requested') return 'revision-requested';
|
|
192
|
-
// PRD awaiting approval overrides
|
|
193
|
-
|
|
192
|
+
// PRD awaiting approval overrides only when no work has started yet — if work is active or
|
|
193
|
+
// done, the approval gate was already implicitly cleared and the status is stale (#970).
|
|
194
|
+
if (prdJsonStatus === 'awaiting-approval' && !hasActiveWork && !allDone) return 'awaiting-approval';
|
|
194
195
|
|
|
195
196
|
// Derive from work item progress
|
|
196
197
|
if (allDone && !hasActiveWork) return 'completed';
|
|
@@ -268,6 +269,10 @@ function renderPlans(plans) {
|
|
|
268
269
|
for (const p of plans) {
|
|
269
270
|
if (p.format === 'prd' && !p.archived && p.sourcePlan) {
|
|
270
271
|
const sourceKeys = [p.sourcePlan, normalizeSourcePlanKey(p.sourcePlan)];
|
|
272
|
+
// Also register the bare basename without directory prefix ('plans/foo.md' → 'foo.md')
|
|
273
|
+
// so that plans whose p.file has no prefix can still find their PRD.
|
|
274
|
+
const bare = (p.sourcePlan || '').replace(/^[^/]+\//, '');
|
|
275
|
+
if (bare && bare !== p.sourcePlan) sourceKeys.push(bare);
|
|
271
276
|
for (const sourceKey of sourceKeys) {
|
|
272
277
|
if (!sourceKey) continue;
|
|
273
278
|
planToPrdFile[sourceKey] = p.file;
|
package/dashboard/js/settings.js
CHANGED
|
@@ -410,6 +410,8 @@ async function openSettings() {
|
|
|
410
410
|
// OFF because killing a foreign process is destructive.
|
|
411
411
|
settingsToggle('Auto-reap orphan worktree holders', 'set-autoReapOrphanWorktreeHolders', !!e.autoReapOrphanWorktreeHolders,
|
|
412
412
|
'When ON, the periodic orphan-worktree sweep automatically kills unambiguous orphan `spawn-agent.js` processes holding a stuck worktree\'s cwd (Windows file-lock pattern, e.g. W-mq1k8z6o003acd89 / PID 16828). Safe-reap criteria: cmdline must match spawn-agent.js, reference the worktree basename, AND process age must exceed `engine.agentTimeout * 2`. Default OFF because killing a foreign process is destructive. The holder SCAN runs either way and is appended to the escalation inbox note.') +
|
|
413
|
+
settingsToggle('Live-checkout auto-reset (fleet-wide)', 'set-liveCheckoutAutoReset', !!e.liveCheckoutAutoReset,
|
|
414
|
+
'When ON, a dirty or broken live-checkout (in-place) tree is force-recovered before dispatch via `git fetch origin` + `git reset --hard origin/<branch>` instead of failing the dispatch with non-retryable LIVE_CHECKOUT_DIRTY. This is the FLEET-WIDE fallback; a per-project `liveCheckoutAutoReset` in config.json overrides it. Default OFF because the reset is DESTRUCTIVE — it discards the operator\'s uncommitted changes in the live checkout (a `live-checkout-autoreset-<workItem>` inbox note records exactly what was discarded for reflog recovery). Only affects projects running in live/in-place checkout mode.') +
|
|
413
415
|
'</div>' +
|
|
414
416
|
'<div class="settings-grid-2">' +
|
|
415
417
|
settingsField('Worktree Create Timeout', 'set-worktreeCreateTimeout', e.worktreeCreateTimeout || 300000, 'ms', 'Timeout for git worktree add (increase for large repos/Windows)') +
|
|
@@ -1082,6 +1084,7 @@ async function saveSettings() {
|
|
|
1082
1084
|
autoCompletePrs: document.getElementById('set-autoCompletePrs').checked,
|
|
1083
1085
|
ccUseWorkerPool: !!document.getElementById('set-ccUseWorkerPool')?.checked,
|
|
1084
1086
|
autoReapOrphanWorktreeHolders: !!document.getElementById('set-autoReapOrphanWorktreeHolders')?.checked,
|
|
1087
|
+
liveCheckoutAutoReset: !!document.getElementById('set-liveCheckoutAutoReset')?.checked,
|
|
1085
1088
|
orphanHolderScanTimeoutMs: document.getElementById('set-orphanHolderScanTimeoutMs')?.value,
|
|
1086
1089
|
statusProbeKillTimeoutMs: document.getElementById('set-statusProbeKillTimeoutMs')?.value,
|
|
1087
1090
|
adoPollEnabled: document.getElementById('set-adoPollEnabled').checked,
|
package/dashboard/slim/body.html
CHANGED
|
@@ -89,6 +89,10 @@
|
|
|
89
89
|
<div class="cockpit-label"><span class="cockpit-dot"></span> Engine</div>
|
|
90
90
|
<div class="cockpit-value dim">—</div>
|
|
91
91
|
<div class="cockpit-detail">checking…</div>
|
|
92
|
+
<!-- Shown by status.js only when the engine is down/stale. Mirrors the
|
|
93
|
+
classic dashboard's stale-banner restart action; both surfaces POST
|
|
94
|
+
/api/engine/restart. -->
|
|
95
|
+
<button id="slim-engine-restart" class="engine-restart-btn" type="button" hidden>Restart engine</button>
|
|
92
96
|
</div>
|
|
93
97
|
<div class="cockpit-tile" data-tile="queued">
|
|
94
98
|
<div class="cockpit-label"><span class="cockpit-dot"></span> Queued work</div>
|
|
@@ -124,17 +128,6 @@
|
|
|
124
128
|
</div>
|
|
125
129
|
</div>
|
|
126
130
|
</div>
|
|
127
|
-
<!-- Pending work items sub-section: lists work items with
|
|
128
|
-
status === 'pending'. Populated by renderPendingWorkItems() in
|
|
129
|
-
status.js from the /api/work-items poll slice; hidden via the
|
|
130
|
-
`hidden` attribute when there are zero pending items. -->
|
|
131
|
-
<div class="pending-section" id="pending-section" hidden>
|
|
132
|
-
<div class="pending-head">
|
|
133
|
-
Pending Work Items
|
|
134
|
-
<span class="pending-count" id="pending-count"></span>
|
|
135
|
-
</div>
|
|
136
|
-
<div class="pending-list" id="pending-list"></div>
|
|
137
|
-
</div>
|
|
138
131
|
</div>
|
|
139
132
|
</div>
|
|
140
133
|
|
|
@@ -189,6 +189,71 @@
|
|
|
189
189
|
body.appendChild(section);
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
+
// Collapsible "Pending work items" section appended to the queued popout
|
|
193
|
+
// (W-mqv1xbcm). Sources data.workItems filtered to status === 'pending' — the
|
|
194
|
+
// same /api/work-items slice the slim poll merges onto the snapshot — so it
|
|
195
|
+
// adds no new API calls and refreshes whenever the popout is re-opened.
|
|
196
|
+
// Expanded by DEFAULT (more prominent than the collapsed Completed section)
|
|
197
|
+
// since these are the queued WIs the operator most wants to scan. Mirrors
|
|
198
|
+
// renderCompletedSection's structure + tileCard usage; all text via
|
|
199
|
+
// textContent (no innerHTML) to keep the SEC-03 baseline clean. Each card
|
|
200
|
+
// shows title, id, project (project || _source), type, and the engine's
|
|
201
|
+
// _pendingReason as a chip when set.
|
|
202
|
+
function renderPendingSection(body, data) {
|
|
203
|
+
var pending = (Array.isArray(data.workItems) ? data.workItems : []).filter(function(w) {
|
|
204
|
+
return w && w.status === 'pending';
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
var section = document.createElement('div');
|
|
208
|
+
section.className = 'tile-completed-section';
|
|
209
|
+
|
|
210
|
+
var head = document.createElement('div');
|
|
211
|
+
head.className = 'tile-completed-section-head';
|
|
212
|
+
head.setAttribute('role', 'button');
|
|
213
|
+
head.setAttribute('tabindex', '0');
|
|
214
|
+
head.setAttribute('aria-expanded', 'true');
|
|
215
|
+
var caret = document.createElement('span');
|
|
216
|
+
caret.className = 'tile-caret';
|
|
217
|
+
caret.textContent = '▾';
|
|
218
|
+
var label = document.createElement('span');
|
|
219
|
+
label.className = 'tile-completed-section-title';
|
|
220
|
+
label.textContent = 'Pending work items (' + pending.length + ')';
|
|
221
|
+
head.appendChild(caret);
|
|
222
|
+
head.appendChild(label);
|
|
223
|
+
|
|
224
|
+
var listWrap = document.createElement('div');
|
|
225
|
+
listWrap.className = 'tile-completed-section-body';
|
|
226
|
+
|
|
227
|
+
if (!pending.length) {
|
|
228
|
+
tileEmpty(listWrap, 'No pending work items.');
|
|
229
|
+
} else {
|
|
230
|
+
pending.forEach(function(w) {
|
|
231
|
+
var project = w.project || w._source;
|
|
232
|
+
var meta = [w.id || null, project || null, w.type || null].filter(Boolean).join(' · ');
|
|
233
|
+
var card = { title: w.title || w.id || '(untitled)', meta: meta };
|
|
234
|
+
if (w._pendingReason) {
|
|
235
|
+
card.chip = { text: String(w._pendingReason).replace(/_/g, ' '), cls: 'amber' };
|
|
236
|
+
}
|
|
237
|
+
listWrap.appendChild(tileCard(card));
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function toggle() {
|
|
242
|
+
var open = !section.classList.toggle('collapsed');
|
|
243
|
+
head.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
244
|
+
caret.textContent = open ? '▾' : '▸';
|
|
245
|
+
listWrap.hidden = !open;
|
|
246
|
+
}
|
|
247
|
+
head.addEventListener('click', toggle);
|
|
248
|
+
head.addEventListener('keydown', function(ev) {
|
|
249
|
+
if (ev.key === 'Enter' || ev.key === ' ') { ev.preventDefault(); toggle(); }
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
section.appendChild(head);
|
|
253
|
+
section.appendChild(listWrap);
|
|
254
|
+
body.appendChild(section);
|
|
255
|
+
}
|
|
256
|
+
|
|
192
257
|
function renderPrTileBody(body, data) {
|
|
193
258
|
// Emoji glyphs make build/review status scannable at a glance; the
|
|
194
259
|
// accompanying label keeps the row readable when the glyph alone is
|
|
@@ -310,7 +375,7 @@
|
|
|
310
375
|
var TILE_VIEWS = {
|
|
311
376
|
engine: { title: 'Engine', render: renderEngineTileBody },
|
|
312
377
|
dispatches: { title: 'Active dispatches', render: function(body, data) { renderDispatchTileBody(body, data, true); } },
|
|
313
|
-
queued: { title: 'Queued work', render: function(body, data) { renderDispatchTileBody(body, data, false); renderCompletedSection(body, data); } },
|
|
378
|
+
queued: { title: 'Queued work', render: function(body, data) { renderDispatchTileBody(body, data, false); renderPendingSection(body, data); renderCompletedSection(body, data); } },
|
|
314
379
|
prs: { title: 'Pull requests', render: renderPrTileBody },
|
|
315
380
|
watches: { title: 'Watches', render: renderWatchTileBody },
|
|
316
381
|
};
|
|
@@ -54,6 +54,68 @@
|
|
|
54
54
|
// Latest /api/status snapshot — read by the cockpit-tile detail modals.
|
|
55
55
|
var lastStatusData = null;
|
|
56
56
|
|
|
57
|
+
// ── Engine restart affordance ──────────────────────────────────
|
|
58
|
+
// Shows a "Restart engine" button on the engine tile when the engine reports
|
|
59
|
+
// down/stale and calls the same POST /api/engine/restart endpoint the classic
|
|
60
|
+
// dashboard uses (dashboard/js/render-dispatch.js#_wireEngineRestartClick).
|
|
61
|
+
// Optimistic UX parity: disable while pending, re-enable + alert on error,
|
|
62
|
+
// re-poll on success (the button hides itself once the engine reports
|
|
63
|
+
// 'running' again). engineRestartPending guards the in-flight label so the
|
|
64
|
+
// 5s status poll doesn't stomp "Restarting…" back to the idle text.
|
|
65
|
+
var engineRestartPending = false;
|
|
66
|
+
|
|
67
|
+
function updateEngineRestartButton(engineDown) {
|
|
68
|
+
var btn = document.getElementById('slim-engine-restart');
|
|
69
|
+
if (!btn) return;
|
|
70
|
+
if (!engineDown) {
|
|
71
|
+
// Engine recovered — clear pending state and hide the button.
|
|
72
|
+
engineRestartPending = false;
|
|
73
|
+
btn.hidden = true;
|
|
74
|
+
btn.disabled = false;
|
|
75
|
+
btn.textContent = 'Restart engine';
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
btn.hidden = false;
|
|
79
|
+
if (!engineRestartPending) {
|
|
80
|
+
btn.disabled = false;
|
|
81
|
+
btn.textContent = 'Restart engine';
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function onEngineRestartClick(ev) {
|
|
86
|
+
if (ev) ev.stopPropagation(); // don't trigger the engine tile's detail modal
|
|
87
|
+
var btn = document.getElementById('slim-engine-restart');
|
|
88
|
+
if (!btn || engineRestartPending) return;
|
|
89
|
+
engineRestartPending = true;
|
|
90
|
+
btn.disabled = true;
|
|
91
|
+
btn.textContent = 'Restarting…';
|
|
92
|
+
try {
|
|
93
|
+
var res = await fetch('/api/engine/restart', { method: 'POST' });
|
|
94
|
+
var data = await res.json().catch(function() { return {}; });
|
|
95
|
+
if (res.ok && data.ok) {
|
|
96
|
+
// Re-poll sooner than the 5s cadence so the engine tile flips to
|
|
97
|
+
// 'running' (and the button auto-hides) shortly after the new PID's
|
|
98
|
+
// first heartbeat. Ongoing 5s polling catches it regardless.
|
|
99
|
+
scheduleStatusRefresh(1500);
|
|
100
|
+
} else {
|
|
101
|
+
engineRestartPending = false;
|
|
102
|
+
btn.disabled = false;
|
|
103
|
+
btn.textContent = 'Restart engine';
|
|
104
|
+
alert('Engine restart failed: ' + (data.error || ('HTTP ' + res.status)));
|
|
105
|
+
}
|
|
106
|
+
} catch (e) {
|
|
107
|
+
engineRestartPending = false;
|
|
108
|
+
btn.disabled = false;
|
|
109
|
+
btn.textContent = 'Restart engine';
|
|
110
|
+
alert('Engine restart failed: ' + (e && e.message ? e.message : 'unknown error'));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
(function bindEngineRestart() {
|
|
115
|
+
var btn = document.getElementById('slim-engine-restart');
|
|
116
|
+
if (btn) btn.addEventListener('click', onEngineRestartClick);
|
|
117
|
+
})();
|
|
118
|
+
|
|
57
119
|
// Reflect the Command Center's resolved runtime + model in the Actions panel
|
|
58
120
|
// header (slim-ux only — classic chrome is untouched). Source is the same
|
|
59
121
|
// /api/status payload the cockpit polls: autoMode.ccModel (config.engine
|
|
@@ -116,12 +178,20 @@
|
|
|
116
178
|
|
|
117
179
|
updateTile('engine', engineValue, engineDetail, engineLit);
|
|
118
180
|
|
|
181
|
+
// Surface a "Restart engine" button on the engine tile whenever the engine
|
|
182
|
+
// is down/unreachable (stopped or stale heartbeat) — mirrors the classic
|
|
183
|
+
// dashboard's stale/stopped restart affordance
|
|
184
|
+
// (dashboard/js/render-dispatch.js#_wireEngineRestartClick). Both front-ends
|
|
185
|
+
// are separate IIFE bundles never loaded together, so the reused unit is the
|
|
186
|
+
// POST /api/engine/restart contract, consistent with the rest of slim (e.g.
|
|
187
|
+
// command-send.js reuses /api/command-center/* rather than the classic fn).
|
|
188
|
+
updateEngineRestartButton(engineState === 'stopped' || engineState === 'stale');
|
|
189
|
+
|
|
119
190
|
// ── Dispatches ─────────────────────────────────────────────
|
|
120
191
|
// The per-minion "working" view moved to the Team cards (renderMembers);
|
|
121
192
|
// this section only drives the system tiles now.
|
|
122
193
|
var dispatch = data.dispatch || {};
|
|
123
194
|
var active = Array.isArray(dispatch.active) ? dispatch.active : [];
|
|
124
|
-
var pending = Array.isArray(dispatch.pending) ? dispatch.pending : [];
|
|
125
195
|
var completed = Array.isArray(dispatch.completed) ? dispatch.completed : [];
|
|
126
196
|
updateTile(
|
|
127
197
|
'dispatches',
|
|
@@ -129,11 +199,19 @@
|
|
|
129
199
|
active.length ? 'agents working' : 'no agents working',
|
|
130
200
|
active.length ? 'amber' : null
|
|
131
201
|
);
|
|
202
|
+
// "Queued work" reflects ALL work items waiting to be dispatched — both
|
|
203
|
+
// status 'pending' (not yet picked up) and status 'queued' (W-mqveaj1h).
|
|
204
|
+
// Sourced from the /api/work-items poll slice merged in by history.js, not
|
|
205
|
+
// the dispatch queue, so a pending WI that has not produced a dispatch yet
|
|
206
|
+
// is still counted. Supersedes the standalone "Pending Work Items" section.
|
|
207
|
+
var waitingItems = (Array.isArray(data.workItems) ? data.workItems : []).filter(function(w) {
|
|
208
|
+
return w && (w.status === 'pending' || w.status === 'queued');
|
|
209
|
+
});
|
|
132
210
|
updateTile(
|
|
133
211
|
'queued',
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
212
|
+
waitingItems.length,
|
|
213
|
+
waitingItems.length ? 'waiting to spawn' : 'queue empty',
|
|
214
|
+
waitingItems.length ? 'orange' : null
|
|
137
215
|
);
|
|
138
216
|
|
|
139
217
|
// ── PR tile (status snapshot from cached pull-requests) ─────
|
|
@@ -179,11 +257,6 @@
|
|
|
179
257
|
// ── Team member cards ──────────────────────────────────────
|
|
180
258
|
renderMembers(Array.isArray(data.agents) ? data.agents : []);
|
|
181
259
|
|
|
182
|
-
// ── Pending work items section ─────────────────────────────
|
|
183
|
-
// Surfaces WIs with status === 'pending' (W-mqtx516q). Sourced from the
|
|
184
|
-
// /api/work-items slice merged in by the slim poll (history.js).
|
|
185
|
-
renderPendingWorkItems(Array.isArray(data.workItems) ? data.workItems : []);
|
|
186
|
-
|
|
187
260
|
// ── Stamps + history ───────────────────────────────────────
|
|
188
261
|
var stamp = document.getElementById('cockpit-stamp');
|
|
189
262
|
if (stamp) stamp.textContent = 'updated ' + relTime(data.timestamp || Date.now());
|
|
@@ -193,94 +266,5 @@
|
|
|
193
266
|
if (hStamp) hStamp.textContent = relTime(data.timestamp || Date.now());
|
|
194
267
|
}
|
|
195
268
|
|
|
196
|
-
// ── Pending work items renderer ────────────────────────────────
|
|
197
|
-
// Lists every work item with status === 'pending' in a dedicated section
|
|
198
|
-
// under the cockpit (W-mqtx516q). The section is hidden via the `hidden`
|
|
199
|
-
// attribute when there are zero pending items. Each row shows id, title,
|
|
200
|
-
// project, type, and the engine's _pendingReason as a small badge when set.
|
|
201
|
-
// Everything goes through textContent (no innerHTML) to keep the SEC-03
|
|
202
|
-
// baseline clean. A JSON fingerprint of the visible fields skips the DOM
|
|
203
|
-
// rebuild when nothing changed between 5s polls.
|
|
204
|
-
var _lastPendingJson = null;
|
|
205
|
-
|
|
206
|
-
function renderPendingWorkItems(items) {
|
|
207
|
-
var section = document.getElementById('pending-section');
|
|
208
|
-
var list = document.getElementById('pending-list');
|
|
209
|
-
if (!section || !list) return;
|
|
210
|
-
|
|
211
|
-
var pending = (Array.isArray(items) ? items : []).filter(function(w) {
|
|
212
|
-
return w && w.status === 'pending';
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
var fingerprint = JSON.stringify(pending.map(function(w) {
|
|
216
|
-
return [w.id, w.title, w.project || w._source, w.type, w._pendingReason];
|
|
217
|
-
}));
|
|
218
|
-
if (fingerprint === _lastPendingJson) return;
|
|
219
|
-
_lastPendingJson = fingerprint;
|
|
220
|
-
|
|
221
|
-
if (!pending.length) {
|
|
222
|
-
section.setAttribute('hidden', '');
|
|
223
|
-
list.textContent = '';
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
section.removeAttribute('hidden');
|
|
227
|
-
|
|
228
|
-
var countEl = document.getElementById('pending-count');
|
|
229
|
-
if (countEl) countEl.textContent = String(pending.length);
|
|
230
|
-
|
|
231
|
-
list.textContent = '';
|
|
232
|
-
var frag = document.createDocumentFragment();
|
|
233
|
-
pending.forEach(function(w) {
|
|
234
|
-
var row = document.createElement('div');
|
|
235
|
-
row.className = 'pending-row';
|
|
236
|
-
|
|
237
|
-
// Top line: title + optional pending-reason badge.
|
|
238
|
-
var main = document.createElement('div');
|
|
239
|
-
main.className = 'pending-row-main';
|
|
240
|
-
|
|
241
|
-
var title = document.createElement('span');
|
|
242
|
-
title.className = 'pending-title';
|
|
243
|
-
title.textContent = w.title || w.id || '(untitled)';
|
|
244
|
-
title.title = (w.title || w.id || '') + (w.id ? ' (' + w.id + ')' : '');
|
|
245
|
-
main.appendChild(title);
|
|
246
|
-
|
|
247
|
-
if (w._pendingReason) {
|
|
248
|
-
var badge = document.createElement('span');
|
|
249
|
-
badge.className = 'pending-reason-badge';
|
|
250
|
-
badge.textContent = String(w._pendingReason).replace(/_/g, ' ');
|
|
251
|
-
badge.title = 'Pending reason: ' + w._pendingReason;
|
|
252
|
-
main.appendChild(badge);
|
|
253
|
-
}
|
|
254
|
-
row.appendChild(main);
|
|
255
|
-
|
|
256
|
-
// Meta line: id + project + type chips.
|
|
257
|
-
var meta = document.createElement('div');
|
|
258
|
-
meta.className = 'pending-row-meta';
|
|
259
|
-
|
|
260
|
-
var idEl = document.createElement('span');
|
|
261
|
-
idEl.className = 'pending-meta-id';
|
|
262
|
-
idEl.textContent = w.id || '';
|
|
263
|
-
meta.appendChild(idEl);
|
|
264
|
-
|
|
265
|
-
var project = w.project || w._source;
|
|
266
|
-
if (project) {
|
|
267
|
-
var projEl = document.createElement('span');
|
|
268
|
-
projEl.className = 'pending-meta-chip';
|
|
269
|
-
projEl.textContent = project;
|
|
270
|
-
meta.appendChild(projEl);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
if (w.type) {
|
|
274
|
-
var typeEl = document.createElement('span');
|
|
275
|
-
typeEl.className = 'pending-meta-chip pending-meta-type';
|
|
276
|
-
typeEl.textContent = w.type;
|
|
277
|
-
meta.appendChild(typeEl);
|
|
278
|
-
}
|
|
279
|
-
row.appendChild(meta);
|
|
280
|
-
|
|
281
|
-
frag.appendChild(row);
|
|
282
|
-
});
|
|
283
|
-
list.appendChild(frag);
|
|
284
|
-
}
|
|
285
269
|
|
|
286
270
|
|
|
@@ -1100,6 +1100,19 @@
|
|
|
1100
1100
|
margin-left: auto;
|
|
1101
1101
|
text-align: right;
|
|
1102
1102
|
}
|
|
1103
|
+
/* Restart-engine affordance on the engine tile — toggled visible via the
|
|
1104
|
+
`hidden` attribute in status.js only when the engine reports down/stale.
|
|
1105
|
+
Mirrors the classic dashboard's stale-banner "Restart engine" action
|
|
1106
|
+
(dashboard/js/render-dispatch.js); both POST /api/engine/restart. */
|
|
1107
|
+
.engine-restart-btn {
|
|
1108
|
+
font-size: var(--text-base); font-weight: 600;
|
|
1109
|
+
padding: 2px 10px; border-radius: 10px;
|
|
1110
|
+
background: rgba(248, 81, 73, 0.12); color: var(--red); border: 1px solid var(--red);
|
|
1111
|
+
cursor: pointer; font-family: inherit; white-space: nowrap;
|
|
1112
|
+
}
|
|
1113
|
+
.engine-restart-btn:hover { filter: brightness(1.2); }
|
|
1114
|
+
.engine-restart-btn:disabled { opacity: 0.6; cursor: default; }
|
|
1115
|
+
.engine-restart-btn[hidden] { display: none; }
|
|
1103
1116
|
/* Cockpit tiles are clickable -> detail modal. */
|
|
1104
1117
|
.cockpit-tile { cursor: pointer; position: relative; }
|
|
1105
1118
|
.cockpit-tile:hover { border-color: var(--blue); }
|
|
@@ -1139,93 +1152,6 @@
|
|
|
1139
1152
|
.team-section { margin-bottom: 14px; }
|
|
1140
1153
|
.cockpit-section { padding-top: 14px; border-top: 1px solid var(--border); }
|
|
1141
1154
|
|
|
1142
|
-
/* Pending work items section (W-mqtx516q). Sits under the cockpit tiles;
|
|
1143
|
-
hidden via the `hidden` attribute when there are no pending WIs. */
|
|
1144
|
-
.pending-section {
|
|
1145
|
-
padding-top: 14px;
|
|
1146
|
-
margin-top: 14px;
|
|
1147
|
-
border-top: 1px solid var(--border);
|
|
1148
|
-
}
|
|
1149
|
-
.pending-section[hidden] { display: none; }
|
|
1150
|
-
.pending-head {
|
|
1151
|
-
font-size: var(--text-sm);
|
|
1152
|
-
font-weight: 600;
|
|
1153
|
-
color: var(--text);
|
|
1154
|
-
letter-spacing: 0.02em;
|
|
1155
|
-
text-transform: uppercase;
|
|
1156
|
-
margin-bottom: 8px;
|
|
1157
|
-
display: flex;
|
|
1158
|
-
align-items: center;
|
|
1159
|
-
gap: 6px;
|
|
1160
|
-
}
|
|
1161
|
-
.pending-count {
|
|
1162
|
-
font-size: var(--text-xs);
|
|
1163
|
-
font-weight: 600;
|
|
1164
|
-
color: var(--orange);
|
|
1165
|
-
background: rgba(214, 153, 34, 0.14);
|
|
1166
|
-
border-radius: 9px;
|
|
1167
|
-
padding: 0 7px;
|
|
1168
|
-
line-height: 16px;
|
|
1169
|
-
}
|
|
1170
|
-
.pending-list {
|
|
1171
|
-
display: flex;
|
|
1172
|
-
flex-direction: column;
|
|
1173
|
-
gap: 6px;
|
|
1174
|
-
}
|
|
1175
|
-
.pending-row {
|
|
1176
|
-
background: var(--surface2);
|
|
1177
|
-
border: 1px solid var(--border);
|
|
1178
|
-
border-left: 3px solid var(--orange);
|
|
1179
|
-
border-radius: var(--radius);
|
|
1180
|
-
padding: 7px 9px;
|
|
1181
|
-
}
|
|
1182
|
-
.pending-row-main {
|
|
1183
|
-
display: flex;
|
|
1184
|
-
align-items: center;
|
|
1185
|
-
gap: 6px;
|
|
1186
|
-
}
|
|
1187
|
-
.pending-title {
|
|
1188
|
-
font-size: var(--text-md);
|
|
1189
|
-
color: var(--text);
|
|
1190
|
-
overflow: hidden;
|
|
1191
|
-
text-overflow: ellipsis;
|
|
1192
|
-
white-space: nowrap;
|
|
1193
|
-
min-width: 0;
|
|
1194
|
-
flex: 1;
|
|
1195
|
-
}
|
|
1196
|
-
.pending-reason-badge {
|
|
1197
|
-
flex: none;
|
|
1198
|
-
font-size: var(--text-xs);
|
|
1199
|
-
color: var(--orange);
|
|
1200
|
-
border: 1px solid rgba(214, 153, 34, 0.5);
|
|
1201
|
-
border-radius: 6px;
|
|
1202
|
-
padding: 0 5px;
|
|
1203
|
-
line-height: 15px;
|
|
1204
|
-
white-space: nowrap;
|
|
1205
|
-
}
|
|
1206
|
-
.pending-row-meta {
|
|
1207
|
-
display: flex;
|
|
1208
|
-
align-items: center;
|
|
1209
|
-
flex-wrap: wrap;
|
|
1210
|
-
gap: 6px;
|
|
1211
|
-
margin-top: 4px;
|
|
1212
|
-
}
|
|
1213
|
-
.pending-meta-id {
|
|
1214
|
-
font-size: var(--text-xs);
|
|
1215
|
-
color: var(--muted);
|
|
1216
|
-
font-family: var(--mono, monospace);
|
|
1217
|
-
}
|
|
1218
|
-
.pending-meta-chip {
|
|
1219
|
-
font-size: var(--text-xs);
|
|
1220
|
-
color: var(--muted);
|
|
1221
|
-
background: var(--surface);
|
|
1222
|
-
border: 1px solid var(--border);
|
|
1223
|
-
border-radius: 6px;
|
|
1224
|
-
padding: 0 6px;
|
|
1225
|
-
line-height: 15px;
|
|
1226
|
-
}
|
|
1227
|
-
.pending-meta-type { color: var(--blue); }
|
|
1228
|
-
|
|
1229
1155
|
/* Square member cards. Auto-fill so the column count adapts to the
|
|
1230
1156
|
panel width; aspect-ratio keeps each card square regardless. */
|
|
1231
1157
|
.member-grid {
|