@yemi33/minions 0.1.2194 → 0.1.2196
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/command-center.js +2 -0
- package/dashboard/js/fre.js +125 -1
- package/dashboard/js/modal.js +35 -1
- package/dashboard/js/refresh.js +7 -1
- package/dashboard/js/render-agents.js +18 -0
- package/dashboard/js/render-kb.js +27 -2
- package/dashboard/js/render-meetings.js +36 -3
- package/dashboard/js/render-pipelines.js +15 -10
- package/dashboard/js/render-plans.js +73 -3
- package/dashboard/js/render-prd.js +29 -5
- package/dashboard/js/render-prs.js +174 -3
- package/dashboard/js/render-schedules.js +12 -0
- package/dashboard/js/render-utils.js +151 -1
- package/dashboard/js/render-watches.js +61 -7
- package/dashboard/js/render-work-items.js +161 -20
- package/dashboard/js/state.js +110 -1
- package/dashboard/js/utils.js +246 -14
- package/dashboard/layout.html +2 -0
- package/dashboard/slim/body.html +23 -13
- package/dashboard/slim/js/knowledge.js +576 -0
- package/dashboard/slim/js/members.js +43 -0
- package/dashboard/slim/js/modals-tiles.js +6 -4
- package/dashboard/slim/js/pinned.js +7 -19
- package/dashboard/slim/js/status.js +17 -21
- package/dashboard/slim/styles.css +94 -36
- package/dashboard/styles.css +34 -0
- package/dashboard-build.js +1 -1
- package/dashboard.js +50 -2
- package/engine/lifecycle.js +6 -0
- package/engine/pipeline.js +10 -0
- package/engine/queries.js +81 -0
- package/engine/scheduler.js +19 -1
- package/package.json +1 -1
|
@@ -204,7 +204,8 @@ function prRow(pr) {
|
|
|
204
204
|
'<td><span class="pr-badge ' + statusClass + '" title="' + escapeHtml(statusLabel) + '">' + escapeHtml(statusLabel) + '</span></td>' +
|
|
205
205
|
'<td>' + (observeBtn || '<span style="color:var(--muted);font-size:var(--text-base)">—</span>') + '</td>' +
|
|
206
206
|
'<td><span class="pr-date" title="' + escapeHtml(createdLabel) + '">' + escapeHtml(createdLabel) + '</span></td>' +
|
|
207
|
-
'<td><button class="
|
|
207
|
+
'<td><button class="pr-info-pill" data-pr-id="' + escapeHtml(String(prId)) + '" onclick="event.stopPropagation();openPrDetail(this.dataset.prId)" title="Open PR detail (in-stack modal)">↗</button>' +
|
|
208
|
+
'<button class="btn-destructive" style="padding:1px 5px;margin-left:4px" data-pr-id="' + escapeHtml(String(prId)) + '" onclick="event.stopPropagation();unlinkPr(this.dataset.prId)" title="Remove from tracking">x</button></td>' +
|
|
208
209
|
'</tr>';
|
|
209
210
|
}
|
|
210
211
|
|
|
@@ -224,7 +225,7 @@ const PRS_COLGROUP =
|
|
|
224
225
|
'<col style="width:110px">' + // Status
|
|
225
226
|
'<col style="width:100px">' + // Observe
|
|
226
227
|
'<col style="width:130px">' + // Created
|
|
227
|
-
'<col style="width:
|
|
228
|
+
'<col style="width:80px">' + // Actions (info-pill + remove)
|
|
228
229
|
'</colgroup>';
|
|
229
230
|
|
|
230
231
|
function prTableHtml(rows) {
|
|
@@ -300,6 +301,176 @@ function openAllPrs() {
|
|
|
300
301
|
document.getElementById('modal').classList.add('open');
|
|
301
302
|
}
|
|
302
303
|
|
|
304
|
+
// P-79b47b0c — in-stack PR detail modal. Routed from openArtifact('pr', id)
|
|
305
|
+
// in render-utils.js and from the PR-row info-pill in prRow(). Read-only:
|
|
306
|
+
// renders title, status/review/build badges, reviewers, linked-WI chips
|
|
307
|
+
// (joined via item._pr === pr.id from queries enrichment), description, and
|
|
308
|
+
// an "Open on GitHub" secondary button. No POSTs from this modal.
|
|
309
|
+
function openPrDetail(prId) {
|
|
310
|
+
if (window.getSelection && window.getSelection().toString().length > 0) return;
|
|
311
|
+
if (!prId) return;
|
|
312
|
+
// Render an immediate skeleton from the in-memory PR cache (allPrs) so the
|
|
313
|
+
// modal feels instant. Then hit GET /api/prs/<id> for the canonical record
|
|
314
|
+
// and re-render via withTopFrame() to respect modal-stack semantics.
|
|
315
|
+
const cached = (allPrs || []).find(p => p && p.id === prId) ||
|
|
316
|
+
((window._lastPrs) || []).find(p => p && p.id === prId);
|
|
317
|
+
document.getElementById('modal-title').textContent = (cached && (cached.title || cached.id)) || prId;
|
|
318
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: _renderPrDetail() escapes all user fields (title, description, agent, reviewers, branch) via escapeHtml() / renderMd() / renderArtifactLink() before assembly
|
|
319
|
+
document.getElementById('modal-body').innerHTML = _renderPrDetail(cached || { id: prId, _loading: true });
|
|
320
|
+
document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
|
|
321
|
+
document.getElementById('modal-body').style.whiteSpace = 'normal';
|
|
322
|
+
document.getElementById('modal').classList.add('open');
|
|
323
|
+
|
|
324
|
+
fetch('/api/prs/' + encodeURIComponent(prId))
|
|
325
|
+
.then(function(r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
|
|
326
|
+
.then(function(data) {
|
|
327
|
+
const pr = data && data.pr;
|
|
328
|
+
if (!pr) return;
|
|
329
|
+
const apply = function() {
|
|
330
|
+
document.getElementById('modal-title').textContent = pr.title || pr.id || prId;
|
|
331
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: _renderPrDetail() escapes all user fields (title, description, agent, reviewers, branch) via escapeHtml() / renderMd() / renderArtifactLink() before assembly
|
|
332
|
+
document.getElementById('modal-body').innerHTML = _renderPrDetail(pr);
|
|
333
|
+
};
|
|
334
|
+
if (typeof withTopFrame === 'function') withTopFrame('pr', prId, apply);
|
|
335
|
+
else apply();
|
|
336
|
+
})
|
|
337
|
+
.catch(function() {
|
|
338
|
+
const body = document.getElementById('modal-body');
|
|
339
|
+
if (body) {
|
|
340
|
+
body.innerHTML = '<p style="color:var(--red)">Failed to load PR ' + escapeHtml(String(prId)) + '.</p>';
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function _renderPrDetail(pr) {
|
|
346
|
+
if (!pr) return '<p style="color:var(--muted)">No data.</p>';
|
|
347
|
+
if (pr._loading) {
|
|
348
|
+
return '<p style="color:var(--muted)">Loading PR ' + escapeHtml(String(pr.id || '')) + '...</p>';
|
|
349
|
+
}
|
|
350
|
+
const sq = pr.minionsReview || {};
|
|
351
|
+
const reviewLabel = sq.status === 'waiting' ? 'reviewing (minions)' : sq.status ? sq.status + ' (minions)' : (pr.reviewStatus || 'pending');
|
|
352
|
+
const reviewClass = (sq.status || pr.reviewStatus) === 'approved' ? 'approved' :
|
|
353
|
+
((sq.status || pr.reviewStatus) === 'changes-requested' || (sq.status || pr.reviewStatus) === 'rejected') ? 'rejected' :
|
|
354
|
+
(sq.status || pr.reviewStatus) === 'waiting' ? 'building' : 'draft';
|
|
355
|
+
const buildLabel = pr.buildStatus || 'none';
|
|
356
|
+
const buildClass = pr._buildStatusStale ? 'build-stale' :
|
|
357
|
+
pr.buildStatus === 'passing' ? 'build-pass' :
|
|
358
|
+
pr.buildStatus === 'failing' ? 'build-fail' :
|
|
359
|
+
pr.buildStatus === 'running' ? 'building' : 'no-build';
|
|
360
|
+
const statusLabel = pr.status || 'active';
|
|
361
|
+
const statusClass = statusLabel === 'merged' ? 'pr-merged' :
|
|
362
|
+
statusLabel === 'abandoned' ? 'pr-abandoned' :
|
|
363
|
+
statusLabel === 'draft' ? 'draft' : 'pr-active';
|
|
364
|
+
|
|
365
|
+
const badges =
|
|
366
|
+
'<span class="pr-badge ' + statusClass + '" title="status">' + escapeHtml(String(statusLabel)) + '</span> ' +
|
|
367
|
+
'<span class="pr-badge ' + reviewClass + '" title="review">' + escapeHtml(String(reviewLabel)) + '</span> ' +
|
|
368
|
+
'<span class="pr-badge ' + buildClass + '" title="build">' + escapeHtml(String(buildLabel)) + '</span>';
|
|
369
|
+
|
|
370
|
+
// Linked WIs — join via item._pr === pr.id (stamped by engine/queries.js).
|
|
371
|
+
const wis = (window._lastWorkItems) || [];
|
|
372
|
+
const linkedWis = wis.filter(w => w && w._pr === pr.id);
|
|
373
|
+
let linkedChips = '';
|
|
374
|
+
if (linkedWis.length && typeof renderArtifactLink === 'function') {
|
|
375
|
+
linkedChips = '<div style="margin-top:10px"><strong style="color:var(--muted);font-size:var(--text-base)">Linked Work Items:</strong><br>' +
|
|
376
|
+
linkedWis.map(w => renderArtifactLink({ type: 'wi', id: w.id, label: w.id, title: w.title || w.id })).join(' ') +
|
|
377
|
+
'</div>';
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// P-e6093f70 — PR ↔ PR linkage. Surface (a) a "Parent PR (follow-up)"
|
|
381
|
+
// chip when this PR record carries meta.pr_followup.parent_pr_url /
|
|
382
|
+
// parent_pr_id (mirrors the WI-side rendering at render-work-items.js
|
|
383
|
+
// _wiRenderDetail, P-79b47b0c) and (b) one chip per pr.depends_on[]
|
|
384
|
+
// entry. Both surfaces use renderArtifactLink({type:'pr'}) so click
|
|
385
|
+
// opens the parent / dep PR modal in-stack with browser-Back support.
|
|
386
|
+
//
|
|
387
|
+
// Graceful degradation: when parent_pr_url cannot be parsed into a
|
|
388
|
+
// canonical id (unknown host), fall back to a plain external <a>.
|
|
389
|
+
// depends_on chips consult the local PR cache (allPrs / window._lastPrs
|
|
390
|
+
// / window._lastPullRequests) and pass deleted:true when the dep is
|
|
391
|
+
// unknown so the chip renders struck-out (non-clickable) instead of
|
|
392
|
+
// silently linking to a 404.
|
|
393
|
+
let parentChip = '';
|
|
394
|
+
const followup = pr.meta && pr.meta.pr_followup;
|
|
395
|
+
if (followup && (followup.parent_pr_id || followup.parent_pr_url) && typeof renderArtifactLink === 'function') {
|
|
396
|
+
let parentPrId = followup.parent_pr_id || '';
|
|
397
|
+
if (!parentPrId && followup.parent_pr_url && typeof _wiDeriveCanonicalPrIdFromUrl === 'function') {
|
|
398
|
+
parentPrId = _wiDeriveCanonicalPrIdFromUrl(followup.parent_pr_url) || '';
|
|
399
|
+
}
|
|
400
|
+
if (parentPrId) {
|
|
401
|
+
parentChip = '<div style="margin-top:10px"><strong style="color:var(--muted);font-size:var(--text-base)">Parent PR (follow-up):</strong> ' +
|
|
402
|
+
renderArtifactLink({ type: 'pr', id: parentPrId, label: parentPrId, title: 'Follow-up from ' + (followup.parent_pr_url || parentPrId) }) +
|
|
403
|
+
'</div>';
|
|
404
|
+
} else if (followup.parent_pr_url) {
|
|
405
|
+
parentChip = '<div style="margin-top:10px"><strong style="color:var(--muted);font-size:var(--text-base)">Parent PR (follow-up):</strong> ' +
|
|
406
|
+
'<a href="' + escapeHtml(followup.parent_pr_url) + '" target="_blank" rel="noopener" style="color:var(--blue)">' + escapeHtml(followup.parent_pr_url) + '</a>' +
|
|
407
|
+
'</div>';
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
let dependsChips = '';
|
|
412
|
+
if (Array.isArray(pr.depends_on) && pr.depends_on.length && typeof renderArtifactLink === 'function') {
|
|
413
|
+
const knownPrIds = new Set();
|
|
414
|
+
const knownSources = [
|
|
415
|
+
(typeof allPrs !== 'undefined' && Array.isArray(allPrs)) ? allPrs : null,
|
|
416
|
+
window._lastPrs,
|
|
417
|
+
window._lastPullRequests,
|
|
418
|
+
];
|
|
419
|
+
for (const src of knownSources) {
|
|
420
|
+
if (Array.isArray(src)) for (const p of src) { if (p && p.id) knownPrIds.add(p.id); }
|
|
421
|
+
}
|
|
422
|
+
dependsChips = '<div style="margin-top:10px"><strong style="color:var(--muted);font-size:var(--text-base)">Depends On:</strong> ' +
|
|
423
|
+
pr.depends_on.map(function(d) {
|
|
424
|
+
const depId = String(d);
|
|
425
|
+
const isDeleted = knownPrIds.size > 0 && !knownPrIds.has(depId);
|
|
426
|
+
return renderArtifactLink({
|
|
427
|
+
type: 'pr',
|
|
428
|
+
id: depId,
|
|
429
|
+
label: depId,
|
|
430
|
+
title: isDeleted ? 'Dependency PR ' + depId + ' is no longer in local state' : 'Depends on ' + depId,
|
|
431
|
+
deleted: isDeleted,
|
|
432
|
+
});
|
|
433
|
+
}).join(' ') +
|
|
434
|
+
'</div>';
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Reviewers list
|
|
438
|
+
let reviewers = '';
|
|
439
|
+
if (Array.isArray(pr.reviewers) && pr.reviewers.length) {
|
|
440
|
+
reviewers = '<div style="margin-top:6px"><strong style="color:var(--muted);font-size:var(--text-base)">Reviewers:</strong> ' +
|
|
441
|
+
pr.reviewers.map(r => '<code>' + escapeHtml(String(typeof r === 'string' ? r : (r.displayName || r.name || ''))) + '</code>').join(' ') +
|
|
442
|
+
'</div>';
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const agent = pr.agent ? '<div><strong style="color:var(--muted);font-size:var(--text-base)">Agent:</strong> ' + escapeHtml(String(pr.agent)) + '</div>' : '';
|
|
446
|
+
const branch = pr.branch ? '<div><strong style="color:var(--muted);font-size:var(--text-base)">Branch:</strong> <code>' + escapeHtml(String(pr.branch)) + '</code></div>' : '';
|
|
447
|
+
const created = pr.created ? '<div><strong style="color:var(--muted);font-size:var(--text-base)">Created:</strong> ' + escapeHtml(String(pr.created)) + '</div>' : '';
|
|
448
|
+
const project = pr._project ? '<div><strong style="color:var(--muted);font-size:var(--text-base)">Project:</strong> ' + escapeHtml(String(pr._project)) + '</div>' : '';
|
|
449
|
+
|
|
450
|
+
const descSafe = typeof renderMd === 'function' ? renderMd(pr.description || '') : escapeHtml(pr.description || '');
|
|
451
|
+
const descBlock = pr.description ?
|
|
452
|
+
'<div style="margin-top:14px"><strong style="color:var(--muted);font-size:var(--text-base)">Description:</strong><div style="margin-top:6px;font-size:var(--text-md);line-height:1.6">' + descSafe + '</div></div>' :
|
|
453
|
+
'';
|
|
454
|
+
|
|
455
|
+
const openOnHost = pr.url ?
|
|
456
|
+
'<a class="btn-secondary" style="display:inline-block;margin-top:14px;padding:6px 12px;text-decoration:none" href="' + escapeHtml(safeUrl(pr.url)) + '" target="_blank" rel="noopener">Open on GitHub ↗</a>' :
|
|
457
|
+
'';
|
|
458
|
+
|
|
459
|
+
return '<div style="font-family:\'Segoe UI\',system-ui,sans-serif">' +
|
|
460
|
+
'<div style="margin-bottom:10px">' +
|
|
461
|
+
'<code style="color:var(--muted);font-size:var(--text-base)">' + escapeHtml(String(pr.id || '')) + '</code><br>' +
|
|
462
|
+
badges +
|
|
463
|
+
'</div>' +
|
|
464
|
+
agent + branch + project + created +
|
|
465
|
+
reviewers +
|
|
466
|
+
linkedChips +
|
|
467
|
+
parentChip +
|
|
468
|
+
dependsChips +
|
|
469
|
+
descBlock +
|
|
470
|
+
openOnHost +
|
|
471
|
+
'</div>';
|
|
472
|
+
}
|
|
473
|
+
|
|
303
474
|
function openModal(i) {
|
|
304
475
|
const item = inboxData[i];
|
|
305
476
|
if (!item) return;
|
|
@@ -490,4 +661,4 @@ async function resumePausedCause(btn) {
|
|
|
490
661
|
}
|
|
491
662
|
}
|
|
492
663
|
|
|
493
|
-
window.MinionsPrs = { prRow, prTableHtml, renderPrs, prPrev, prNext, openAllPrs, openModal, openAddPrModal, unlinkPr, togglePrObserve, resumePausedCause };
|
|
664
|
+
window.MinionsPrs = { prRow, prTableHtml, renderPrs, prPrev, prNext, openAllPrs, openModal, openAddPrModal, openPrDetail, unlinkPr, togglePrObserve, resumePausedCause };
|
|
@@ -405,6 +405,18 @@ function openScheduleDetail(id) {
|
|
|
405
405
|
'<div><strong style="color:var(--muted)">Agent:</strong> ' + escHtml(s.agent || 'auto') + '</div>' +
|
|
406
406
|
'<div><strong style="color:var(--muted)">Status:</strong> ' + enabledLabel + '</div>' +
|
|
407
407
|
'<div><strong style="color:var(--muted)">Last Run:</strong> ' + escHtml(lastRun) + '</div>' +
|
|
408
|
+
// P-c549d07e — "Recent dispatches" chip row. Each chip routes through
|
|
409
|
+
// renderArtifactLink({type:'wi'}) → openArtifact() so clicking pushes
|
|
410
|
+
// the WI detail modal onto the stack. Hard-cap to 5 chips even if the
|
|
411
|
+
// API returned more (defensive — the API already slices).
|
|
412
|
+
(Array.isArray(s._recentWorkItemIds) && s._recentWorkItemIds.length > 0
|
|
413
|
+
? '<div><strong style="color:var(--muted)">Recent dispatches:</strong>' +
|
|
414
|
+
'<div style="display:flex;flex-wrap:wrap;gap:4px;margin-top:4px">' +
|
|
415
|
+
s._recentWorkItemIds.slice(0, 5).map(function(wi) {
|
|
416
|
+
return renderArtifactLink({ type: 'wi', id: String(wi), label: String(wi), title: 'Open work item ' + wi });
|
|
417
|
+
}).join('') +
|
|
418
|
+
'</div></div>'
|
|
419
|
+
: '') +
|
|
408
420
|
(s.description ? '<div><strong style="color:var(--muted)">Description:</strong><div style="margin-top:4px;padding:8px;background:var(--surface2);border-radius:4px">' + renderMd(s.description) + '</div></div>' : '') +
|
|
409
421
|
'</div>';
|
|
410
422
|
|
|
@@ -575,4 +575,154 @@ function restoreDashboardScrollState(root, state) {
|
|
|
575
575
|
});
|
|
576
576
|
}
|
|
577
577
|
|
|
578
|
-
|
|
578
|
+
// P-ce1e5e47 — universal artifact-modal dispatcher. Chip clicks (and the
|
|
579
|
+
// URL-hash replay path) call openArtifact(type, id); this pushes a stack
|
|
580
|
+
// frame and delegates to the existing per-type opener.
|
|
581
|
+
//
|
|
582
|
+
// Type → handler table:
|
|
583
|
+
// wi → openWorkItemDetail(id)
|
|
584
|
+
// pr → openPrDetail(id) (P-79b47b0c; falls back to openPrUrl)
|
|
585
|
+
// plan/prd → planView(id)
|
|
586
|
+
// note → openInboxNote(id)
|
|
587
|
+
// kb → kbOpenItem(category, file) (id = "category/file")
|
|
588
|
+
// schedule → openScheduleDetail(id)
|
|
589
|
+
// watch → openWatchDetail(id)
|
|
590
|
+
// pipeline → openPipelineDetail(id)
|
|
591
|
+
// meeting → openMeetingDetail(id)
|
|
592
|
+
// agent → openAgentDetail(id) ← detail-panel exception (see below)
|
|
593
|
+
//
|
|
594
|
+
// P-30b6cf8a — Detail-panel exception (intentional, documented). The agent
|
|
595
|
+
// type does NOT push a modal-stack frame and does NOT use the modal shell.
|
|
596
|
+
// openAgentDetail() opens the right-side slide-in detail panel (tabs +
|
|
597
|
+
// live-stream + charter editor) which is a peer of the modal stack, not a
|
|
598
|
+
// member of it. Clicking an agent chip from inside a modal therefore:
|
|
599
|
+
// 1. Resets the modal stack (resetModalStack) and physically closes the
|
|
600
|
+
// modal shell — modal-stack identity goes back to empty.
|
|
601
|
+
// 2. Opens the agent detail panel.
|
|
602
|
+
// The reverse direction is symmetric: clicking a WI/PR/plan chip from
|
|
603
|
+
// inside the panel calls openArtifact('<type>', id), which (per the
|
|
604
|
+
// non-agent branch below) closes the detail panel and starts a fresh
|
|
605
|
+
// modal stack. This one-way exception is the deliberate contract — do
|
|
606
|
+
// NOT "fix" it by adding pushModalFrame for the agent type. See the
|
|
607
|
+
// matching JSDoc on dashboard/js/render-agents.js openAgentDetail().
|
|
608
|
+
function openArtifact(type, id) {
|
|
609
|
+
if (!type || id === undefined || id === null) return;
|
|
610
|
+
if (type === 'agent') {
|
|
611
|
+
if (typeof resetModalStack === 'function') resetModalStack();
|
|
612
|
+
if (typeof _physicallyCloseModal === 'function') _physicallyCloseModal();
|
|
613
|
+
if (typeof openAgentDetail === 'function') openAgentDetail(id);
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
// Non-agent (modal) type: dismiss the detail panel if it's open so the
|
|
617
|
+
// modal-stack peer cleanly takes over the foreground.
|
|
618
|
+
try {
|
|
619
|
+
var panelEl = document.getElementById('detail-panel');
|
|
620
|
+
if (panelEl && panelEl.classList.contains('open') && typeof closeDetail === 'function') {
|
|
621
|
+
closeDetail();
|
|
622
|
+
}
|
|
623
|
+
} catch { /* DOM may not be ready in unit-test eval */ }
|
|
624
|
+
if (typeof pushModalFrame === 'function') {
|
|
625
|
+
try {
|
|
626
|
+
pushModalFrame({ type: type, id: String(id), openFn: openArtifact, openArgs: [type, String(id)] });
|
|
627
|
+
} catch (e) { try { console.error('pushModalFrame failed', e); } catch {} }
|
|
628
|
+
}
|
|
629
|
+
switch (type) {
|
|
630
|
+
case 'wi':
|
|
631
|
+
if (typeof openWorkItemDetail === 'function') openWorkItemDetail(id);
|
|
632
|
+
break;
|
|
633
|
+
case 'pr':
|
|
634
|
+
if (typeof openPrDetail === 'function') openPrDetail(id);
|
|
635
|
+
else if (typeof window !== 'undefined' && window.MinionsPrs && typeof window.MinionsPrs.openPrDetail === 'function') window.MinionsPrs.openPrDetail(id);
|
|
636
|
+
break;
|
|
637
|
+
case 'plan':
|
|
638
|
+
case 'prd':
|
|
639
|
+
if (typeof planView === 'function') planView(id);
|
|
640
|
+
break;
|
|
641
|
+
case 'note':
|
|
642
|
+
if (typeof openInboxNote === 'function') openInboxNote(id);
|
|
643
|
+
break;
|
|
644
|
+
case 'kb': {
|
|
645
|
+
var idx = String(id).indexOf('/');
|
|
646
|
+
if (idx > 0 && typeof kbOpenItem === 'function') {
|
|
647
|
+
kbOpenItem(String(id).slice(0, idx), String(id).slice(idx + 1));
|
|
648
|
+
}
|
|
649
|
+
break;
|
|
650
|
+
}
|
|
651
|
+
case 'schedule':
|
|
652
|
+
if (typeof openScheduleDetail === 'function') openScheduleDetail(id);
|
|
653
|
+
break;
|
|
654
|
+
case 'watch':
|
|
655
|
+
if (typeof openWatchDetail === 'function') openWatchDetail(id);
|
|
656
|
+
break;
|
|
657
|
+
case 'pipeline':
|
|
658
|
+
if (typeof openPipelineDetail === 'function') openPipelineDetail(id);
|
|
659
|
+
break;
|
|
660
|
+
case 'meeting':
|
|
661
|
+
if (typeof openMeetingDetail === 'function') openMeetingDetail(id);
|
|
662
|
+
break;
|
|
663
|
+
default:
|
|
664
|
+
try { console.warn('openArtifact: unknown type', type); } catch {}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// P-e265cd31 — shared artifact-chip renderer. Every chip click routes through
|
|
669
|
+
// openArtifact() so the modal stack push, URL-hash update, and browser-Back
|
|
670
|
+
// integration come for free at every callsite (no more inline
|
|
671
|
+
// pushModalBack(...);openXxxDetail(...) string-built pills).
|
|
672
|
+
//
|
|
673
|
+
// Icon table — keep consistent across the dashboard:
|
|
674
|
+
// wi=📋 pr=🔀 plan=📋 prd=📄 note=📝 kb=📚
|
|
675
|
+
// schedule=⏰ watch=👁 pipeline=⛓ meeting=🗣 agent=🤖
|
|
676
|
+
//
|
|
677
|
+
// Options:
|
|
678
|
+
// type — one of the icon-table keys (case-sensitive)
|
|
679
|
+
// id — artifact identifier; passed verbatim to openArtifact(type, id)
|
|
680
|
+
// label — visible chip text (defaults to id)
|
|
681
|
+
// title — optional hover tooltip (defaults to empty)
|
|
682
|
+
// icon — optional icon override (defaults to ICON_TABLE[type])
|
|
683
|
+
// deleted — true → render struck-out, non-clickable chip so dead refs
|
|
684
|
+
// still show context to the operator
|
|
685
|
+
//
|
|
686
|
+
// Unknown type with no explicit icon falls back to a plain escaped span (no
|
|
687
|
+
// chip styling, no throw) so callers can pass through arbitrary refs safely.
|
|
688
|
+
var _ARTIFACT_ICON_TABLE = {
|
|
689
|
+
wi: '📋', pr: '🔀', plan: '📋', prd: '📄', note: '📝', kb: '📚',
|
|
690
|
+
schedule: '⏰', watch: '👁', pipeline: '⛓', meeting: '🗣', agent: '🤖',
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
function renderArtifactLink(opts) {
|
|
694
|
+
var o = opts || {};
|
|
695
|
+
var type = String(o.type == null ? '' : o.type);
|
|
696
|
+
var id = o.id == null ? '' : String(o.id);
|
|
697
|
+
var label = o.label == null ? id : String(o.label);
|
|
698
|
+
var title = o.title == null ? '' : String(o.title);
|
|
699
|
+
var hasIconOverride = typeof o.icon === 'string' && o.icon.length > 0;
|
|
700
|
+
var icon = hasIconOverride ? o.icon : (_ARTIFACT_ICON_TABLE[type] || '');
|
|
701
|
+
var deleted = o.deleted === true;
|
|
702
|
+
|
|
703
|
+
if (!Object.prototype.hasOwnProperty.call(_ARTIFACT_ICON_TABLE, type) && !hasIconOverride) {
|
|
704
|
+
return '<span class="artifact-chip-unknown">' + escHtml(label) + '</span>';
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
var classes = 'artifact-chip' + (deleted ? ' deleted' : '');
|
|
708
|
+
// JSON.stringify produces a safe JS string literal (handles backslashes,
|
|
709
|
+
// quotes, control chars). escHtml then makes it safe for the attribute
|
|
710
|
+
// value; the HTML parser reverses the entity escapes before the JS
|
|
711
|
+
// evaluator sees them, so the call site always receives the original
|
|
712
|
+
// string values.
|
|
713
|
+
var clickAttr = deleted
|
|
714
|
+
? ''
|
|
715
|
+
: ' onclick="event.stopPropagation();openArtifact('
|
|
716
|
+
+ escHtml(JSON.stringify(type)) + ','
|
|
717
|
+
+ escHtml(JSON.stringify(id)) + ')"';
|
|
718
|
+
var titleAttr = title ? ' title="' + escHtml(title) + '"' : '';
|
|
719
|
+
return '<span class="' + classes + '"'
|
|
720
|
+
+ ' data-art-type="' + escHtml(type) + '"'
|
|
721
|
+
+ ' data-art-id="' + escHtml(id) + '"'
|
|
722
|
+
+ clickAttr + titleAttr + '>'
|
|
723
|
+
+ (icon ? '<span class="artifact-chip-icon">' + escHtml(icon) + '</span>' : '')
|
|
724
|
+
+ '<span class="artifact-chip-label">' + escHtml(label) + '</span>'
|
|
725
|
+
+ '</span>';
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
window.MinionsRenderUtils = { formatToolSummary, renderAgentOutput, renderTerminalBanner, renderPager, pinButton, captureDashboardScrollState, restoreDashboardScrollState, openArtifact, renderArtifactLink };
|
|
@@ -101,6 +101,48 @@ function _targetTypeLabel(type) {
|
|
|
101
101
|
return _WATCH_TARGET_LABELS[type] || (type || '');
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
// P-1acd4cde — map a watch.targetType to the artifact-chip type understood by
|
|
105
|
+
// renderArtifactLink / openArtifact (defined in render-utils.js). Returns null
|
|
106
|
+
// for targetTypes that don't correspond to a navigable artifact (teams-channel,
|
|
107
|
+
// future plugin types) so callers fall back to plain text rendering. The
|
|
108
|
+
// mapping covers the 8 first-class TARGET_TYPES registered in engine/watches.js
|
|
109
|
+
// (engine/shared.js#WATCH_TARGET_TYPE) — dispatch ids are WI ids in practice
|
|
110
|
+
// so they share the wi chip target.
|
|
111
|
+
var _WATCH_TARGET_TO_ART_TYPE = {
|
|
112
|
+
'pr': 'pr',
|
|
113
|
+
'work-item': 'wi',
|
|
114
|
+
'meeting': 'meeting',
|
|
115
|
+
'plan': 'plan',
|
|
116
|
+
'schedule': 'schedule',
|
|
117
|
+
'pipeline': 'pipeline',
|
|
118
|
+
'dispatch': 'wi',
|
|
119
|
+
'agent': 'agent',
|
|
120
|
+
};
|
|
121
|
+
function _watchTargetToArtType(targetType) {
|
|
122
|
+
if (!targetType) return null;
|
|
123
|
+
return _WATCH_TARGET_TO_ART_TYPE[targetType] || null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// P-1acd4cde — render the watch target as a clickable artifact chip when the
|
|
127
|
+
// targetType maps to a known artifact AND the target is a non-empty string id.
|
|
128
|
+
// Object targets (e.g. teams-channel `{teamId, channelId}`) and plugin
|
|
129
|
+
// targetTypes without an artifact equivalent fall back to the legacy
|
|
130
|
+
// _formatWatchTarget plain text so the W-mq1j5f9z00030b8f title-fallback
|
|
131
|
+
// contract still holds for non-artifact watches.
|
|
132
|
+
function _renderWatchTargetChip(w) {
|
|
133
|
+
var artType = _watchTargetToArtType(w && w.targetType);
|
|
134
|
+
if (artType && typeof w.target === 'string' && w.target.length > 0
|
|
135
|
+
&& typeof renderArtifactLink === 'function') {
|
|
136
|
+
return renderArtifactLink({
|
|
137
|
+
type: artType,
|
|
138
|
+
id: w.target,
|
|
139
|
+
label: w.target,
|
|
140
|
+
title: _targetTypeLabel(w.targetType) + ' ' + w.target,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
return escHtml(_formatWatchTarget(w && w.target, w && w.targetType));
|
|
144
|
+
}
|
|
145
|
+
|
|
104
146
|
// W-mq1j5f9z00030b8f — `teams-channel` target is `{teamId, channelId}` (object);
|
|
105
147
|
// other targetTypes use strings. Avoid `String(target)` → "[object Object]";
|
|
106
148
|
// route all target rendering here so future object-target plugins inherit safety.
|
|
@@ -339,7 +381,7 @@ function openWatchDetail(id) {
|
|
|
339
381
|
|
|
340
382
|
var body = '<div style="display:flex;flex-direction:column;gap:10px;font-size:var(--text-md);line-height:1.6">' +
|
|
341
383
|
'<div><strong style="color:var(--muted)">ID:</strong> ' + escHtml(w.id) + '</div>' +
|
|
342
|
-
'<div><strong style="color:var(--muted)">Target:</strong> ' +
|
|
384
|
+
'<div><strong style="color:var(--muted)">Target:</strong> ' + _renderWatchTargetChip(w) + '</div>' +
|
|
343
385
|
'<div><strong style="color:var(--muted)">Target Type:</strong> <span class="dispatch-type explore">' + escHtml(targetLabel) + '</span></div>' +
|
|
344
386
|
'<div><strong style="color:var(--muted)">Condition:</strong> <span style="color:var(--blue)">' + escHtml(condLabel) + '</span></div>' +
|
|
345
387
|
'<div><strong style="color:var(--muted)">Check Interval:</strong> ' + escHtml(_intervalToHuman(w.interval)) + '</div>' +
|
|
@@ -356,7 +398,7 @@ function openWatchDetail(id) {
|
|
|
356
398
|
// P-w14e7a8c — Phase 7.1: render cross-target requirements list when
|
|
357
399
|
// the watch carries a non-empty requires[] from the editor.
|
|
358
400
|
_renderWatchRequiresDetail(w.requires) +
|
|
359
|
-
(w._lastActionResult ? '<div><strong style="color:var(--muted)">Last Action Result:</strong> <span style="color:' + (w._lastActionResult.ok ? 'var(--green)' : 'var(--red)') + '">' + (w._lastActionResult.ok ? 'OK' : 'FAILED') + '</span> — ' + escHtml(w._lastActionResult.summary || '') + (w._lastActionResult.dispatchedItemId ? ' (
|
|
401
|
+
(w._lastActionResult ? '<div><strong style="color:var(--muted)">Last Action Result:</strong> <span style="color:' + (w._lastActionResult.ok ? 'var(--green)' : 'var(--red)') + '">' + (w._lastActionResult.ok ? 'OK' : 'FAILED') + '</span> — ' + escHtml(w._lastActionResult.summary || '') + (w._lastActionResult.dispatchedItemId && typeof renderArtifactLink === 'function' ? ' ' + renderArtifactLink({ type: 'wi', id: String(w._lastActionResult.dispatchedItemId), label: String(w._lastActionResult.dispatchedItemId), title: 'Dispatched work item ' + w._lastActionResult.dispatchedItemId }) : '') + '</div>' : '') +
|
|
360
402
|
'<div><strong style="color:var(--muted)">Created:</strong> ' + escHtml(createdAt) + '</div>' +
|
|
361
403
|
'<div><strong style="color:var(--muted)">Last Checked:</strong> ' + escHtml(lastChecked) + '</div>' +
|
|
362
404
|
'<div><strong style="color:var(--muted)">Last Triggered:</strong> ' + escHtml(lastTriggered) + '</div>' +
|
|
@@ -389,13 +431,22 @@ function openWatchDetail(id) {
|
|
|
389
431
|
return res.json();
|
|
390
432
|
}).then(function(data) {
|
|
391
433
|
if (!container.isConnected) return; // user closed the modal
|
|
392
|
-
//
|
|
393
|
-
|
|
434
|
+
// P-ce1e5e47 — skip the history render when the user has stacked
|
|
435
|
+
// another modal on top of this watch view.
|
|
436
|
+
var apply = function() {
|
|
437
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: _renderWatchHistoryDetail() escapes all user-controlled fields before assembling HTML
|
|
438
|
+
container.innerHTML = _renderWatchHistoryDetail(data && data.history);
|
|
439
|
+
};
|
|
440
|
+
if (typeof withTopFrame === 'function') withTopFrame('watch', w.id, apply);
|
|
441
|
+
else apply();
|
|
394
442
|
}).catch(function() {
|
|
395
443
|
if (!container.isConnected) return;
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
444
|
+
var applyFallback = function() {
|
|
445
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: _renderWatchHistoryDetail() escapes all user-controlled fields before assembling HTML
|
|
446
|
+
container.innerHTML = _renderWatchHistoryDetail(w._history || []);
|
|
447
|
+
};
|
|
448
|
+
if (typeof withTopFrame === 'function') withTopFrame('watch', w.id, applyFallback);
|
|
449
|
+
else applyFallback();
|
|
399
450
|
});
|
|
400
451
|
})();
|
|
401
452
|
}
|
|
@@ -824,4 +875,7 @@ window.MinionsWatches = {
|
|
|
824
875
|
// W-mq1j5f9z00030b8f — target / title fallback helpers exposed for unit coverage.
|
|
825
876
|
_formatWatchTarget: _formatWatchTarget,
|
|
826
877
|
_watchTitleFallback: _watchTitleFallback,
|
|
878
|
+
// P-1acd4cde — watch ↔ target artifact-chip linkage helpers exposed for unit coverage.
|
|
879
|
+
_watchTargetToArtType: _watchTargetToArtType,
|
|
880
|
+
_renderWatchTargetChip: _renderWatchTargetChip,
|
|
827
881
|
};
|