@yemi33/minions 0.1.2424 → 0.1.2425
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/bin/install-internal-minions.js +1209 -0
- package/bin/minions.js +75 -9
- package/dashboard/docs/typography.md +27 -12
- package/dashboard/js/command-center.js +13 -11
- package/dashboard/js/live-stream.js +1 -1
- package/dashboard/js/memory-search.js +388 -43
- package/dashboard/js/modal-qa.js +5 -5
- package/dashboard/js/qa.js +39 -26
- package/dashboard/js/refresh.js +37 -18
- package/dashboard/js/render-agents.js +14 -18
- package/dashboard/js/render-dispatch.js +14 -74
- package/dashboard/js/render-inbox.js +3 -3
- package/dashboard/js/render-meetings.js +8 -8
- package/dashboard/js/render-other.js +45 -20
- package/dashboard/js/render-pipelines.js +6 -6
- package/dashboard/js/render-plans.js +14 -14
- package/dashboard/js/render-prd.js +46 -46
- package/dashboard/js/render-prs.js +9 -65
- package/dashboard/js/render-schedules.js +5 -5
- package/dashboard/js/render-skills.js +6 -6
- package/dashboard/js/render-utils.js +4 -4
- package/dashboard/js/render-watches.js +4 -16
- package/dashboard/js/render-work-items.js +95 -107
- package/dashboard/js/settings.js +175 -74
- package/dashboard/layout.html +3 -2
- package/dashboard/pages/inbox.html +1 -1
- package/dashboard/pages/work.html +1 -1
- package/dashboard/shared/model-display.js +9 -0
- package/dashboard/shared/watches-source.js +43 -0
- package/dashboard/shared/welcome-popup.js +259 -0
- package/dashboard/slim/body.html +6 -6
- package/dashboard/slim/js/chat.js +4 -2
- package/dashboard/slim/js/history.js +10 -3
- package/dashboard/slim/js/modals-tiles.js +85 -16
- package/dashboard/slim/js/status.js +12 -16
- package/dashboard/slim/styles.css +24 -4
- package/dashboard/styles.css +62 -27
- package/dashboard-build.js +8 -2
- package/dashboard.js +3854 -2514
- package/docs/README.md +6 -4
- package/docs/api-errors.md +144 -0
- package/docs/auto-discovery.md +83 -61
- package/docs/capture-demos.js +173 -30
- package/docs/command-center.md +5 -1
- package/docs/completion-reports.md +127 -11
- package/docs/constants.md +15 -3
- package/docs/constellation-style-telemetry.md +2 -2
- package/docs/copilot-cli-schema.md +32 -22
- package/docs/cross-repo-plans.md +30 -30
- package/docs/demo/memory-system.html +1 -1
- package/docs/deprecated.json +132 -18
- package/docs/diagnostics-memory.md +8 -4
- package/docs/human-vs-automated.md +2 -2
- package/docs/internal-install.md +190 -0
- package/docs/kb-pr3223-cascade-archiving.md +16 -0
- package/docs/kb-pr696-merge-conflict-docs.md +23 -0
- package/docs/kb-sweep.md +2 -2
- package/docs/keep-processes.md +8 -1
- package/docs/live-checkout-mode.md +1 -1
- package/docs/managed-spawn.md +21 -3
- package/docs/named-agents.md +3 -2
- package/docs/plan-lifecycle.md +32 -32
- package/docs/pr-screenshots/pr-899/worker-pool-worktrees-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-899/worker-pool-worktrees-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-979/auto-fix-pane-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-979/auto-fix-pane-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-985/pr-column-em-dash-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-985/pr-column-em-dash-BEFORE.png +0 -0
- package/docs/qa-runbook-lifecycle.md +67 -51
- package/docs/qa-runbooks.md +2 -2
- package/docs/runtime-adapters.md +101 -50
- package/docs/security.md +28 -0
- package/docs/self-improvement.md +47 -13
- package/docs/skills.md +29 -0
- package/docs/slim-ux/concepts.md +2 -2
- package/docs/specs/agent-configurability.md +43 -41
- package/docs/specs/agent-rename.md +13 -13
- package/docs/team-memory.md +283 -14
- package/docs/tutorials/03-plan-driven-feature.md +3 -3
- package/docs/tutorials/04-runtimes-and-harness.md +18 -21
- package/docs/tutorials/08-operations-and-recovery.md +3 -2
- package/docs/tutorials/README.md +4 -3
- package/docs/visual-evidence-ci.md +103 -0
- package/docs/watches.md +35 -30
- package/docs/worktree-lifecycle.md +151 -13
- package/engine/acp-transport.js +1 -0
- package/engine/ado.js +80 -33
- package/engine/agent-api-validation.js +571 -0
- package/engine/agent-worker-pool.js +7 -6
- package/engine/api-contracts/agent-content.js +761 -0
- package/engine/api-contracts/cc-ops.js +219 -0
- package/engine/api-contracts/config-runtime.js +419 -0
- package/engine/api-contracts/core.js +138 -0
- package/engine/api-contracts/index.js +506 -0
- package/engine/api-contracts/orchestration.js +602 -0
- package/engine/api-contracts/pull-requests.js +323 -0
- package/engine/api-contracts/qa-process.js +764 -0
- package/engine/api-contracts/work-plan-prd.js +494 -0
- package/engine/api-validation.js +543 -0
- package/engine/cc-api-validation.js +586 -0
- package/engine/cc-worker-pool.js +9 -1
- package/engine/cleanup.js +393 -271
- package/engine/cli.js +65 -7
- package/engine/comment-classifier.js +70 -5
- package/engine/consolidation.js +123 -5
- package/engine/content-api-validation.js +606 -0
- package/engine/create-pr-worktree.js +22 -1
- package/engine/db/migrations/027-review-learning-lifecycle.js +162 -0
- package/engine/dispatch.js +33 -0
- package/engine/distribution.js +189 -0
- package/engine/execution-model.js +59 -0
- package/engine/features.js +12 -0
- package/engine/github.js +67 -25
- package/engine/harness-context.js +313 -0
- package/engine/inbox-store.js +1 -1
- package/engine/kb-sweep.js +161 -38
- package/engine/keep-process-sweep.js +43 -3
- package/engine/lifecycle.js +1066 -165
- package/engine/llm.js +4 -0
- package/engine/managed-spawn.js +463 -48
- package/engine/meeting.js +197 -34
- package/engine/memory-retrieval.js +280 -0
- package/engine/memory-store.js +821 -38
- package/engine/model-discovery.js +17 -3
- package/engine/pipeline.js +802 -14
- package/engine/plan-prd-validation.js +745 -0
- package/engine/playbook.js +206 -17
- package/engine/pooled-agent-process.js +6 -1
- package/engine/pr-action.js +1 -1
- package/engine/pr-issue-validation.js +653 -0
- package/engine/pr-resolve.js +75 -13
- package/engine/prd-store.js +39 -6
- package/engine/preflight.js +7 -2
- package/engine/process-utils.js +79 -31
- package/engine/projects.js +198 -12
- package/engine/promotion.js +371 -0
- package/engine/qa-from-prd.js +3 -0
- package/engine/qa-process-validation.js +572 -0
- package/engine/qa-runbooks.js +41 -37
- package/engine/qa-runners/maestro.js +11 -7
- package/engine/qa-runners/playwright.js +37 -14
- package/engine/qa-runners.js +9 -10
- package/engine/qa-runs.js +124 -11
- package/engine/qa-sessions.js +139 -40
- package/engine/quarantine-refs.js +81 -11
- package/engine/queries.js +163 -9
- package/engine/review-learning-backfill.js +482 -0
- package/engine/review-learning.js +1236 -0
- package/engine/runtimes/claude.js +60 -2
- package/engine/runtimes/codex.js +57 -16
- package/engine/runtimes/contract.js +21 -0
- package/engine/runtimes/copilot.js +51 -3
- package/engine/runtimes/index.js +1 -0
- package/engine/scheduler.js +201 -32
- package/engine/settings-validation.js +909 -0
- package/engine/shared.js +705 -208
- package/engine/spawn-agent.js +48 -21
- package/engine/steering-constraints.js +31 -0
- package/engine/steering-store.js +10 -2
- package/engine/steering.js +14 -2
- package/engine/timeout.js +404 -111
- package/engine/untrusted-fence.js +24 -10
- package/engine/watch-actions.js +7 -2
- package/engine/watches.js +353 -51
- package/engine/work-item-validation.js +561 -0
- package/engine/work-items-store.js +61 -1
- package/engine/worktree-gc.js +656 -37
- package/engine/worktree-preflight.js +154 -0
- package/engine.js +2068 -342
- package/package.json +1 -1
- package/playbooks/_pr-description-audit.md +88 -41
- package/playbooks/build-fix-complex.md +3 -3
- package/playbooks/docs.md +2 -2
- package/playbooks/fix.md +18 -18
- package/playbooks/implement.md +18 -18
- package/playbooks/qa-session-draft.md +10 -12
- package/playbooks/qa-session-execute.md +16 -12
- package/playbooks/qa-validate.md +8 -7
- package/playbooks/shared-rules.md +45 -0
- package/playbooks/test.md +4 -0
- package/prompts/cc-system.md +1 -1
- package/skills/check-self-authored-review-comment/SKILL.md +2 -2
package/dashboard/layout.html
CHANGED
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
<div style="display:flex;align-items:center;gap:8px;">
|
|
41
41
|
<button id="try-slim-ux-btn" class="try-slim-ux-btn" onclick="trySlimUx(this)" title="Switch to the experimental Slim UX — a focused, single-screen cockpit" style="display:none">✨ Try new Slim UX</button>
|
|
42
42
|
<span class="engine-badge stopped" id="engine-badge">STOPPED</span>
|
|
43
|
-
<span id="version-banner" style="font-size:var(--text-
|
|
43
|
+
<span id="version-banner" style="font-size:var(--text-sm);color:var(--muted)"></span>
|
|
44
44
|
<div class="timestamp" id="ts">—</div>
|
|
45
45
|
<button onclick="toggleCommandCenter()" style="background:none;border:none;cursor:pointer;font-size:var(--text-md);color:var(--blue);padding:4px 10px;line-height:1;font-weight:700;border:1px solid var(--blue);border-radius:4px" title="Command Center" id="cc-toggle-btn">Command Center</button>
|
|
46
46
|
<button onclick="openBugReport()" style="background:none;border:none;cursor:pointer;font-size:var(--text-base);color:var(--muted);padding:4px 8px;line-height:1;border:1px solid var(--border);border-radius:4px" title="Report a bug in Minions">Report Bug</button>
|
|
@@ -72,13 +72,14 @@
|
|
|
72
72
|
<div id="cc-tab-bar" style="display:flex;gap:8px;padding:4px 12px;border-bottom:1px solid var(--border);overflow:hidden;align-items:center;flex-shrink:0"></div>
|
|
73
73
|
<div id="cc-messages" style="flex:1;overflow-y:auto;padding:12px 16px;display:flex;flex-direction:column;gap:10px"></div>
|
|
74
74
|
<div style="padding:12px 16px;border-top:1px solid var(--border)">
|
|
75
|
+
<div id="cc-suggestions-host"></div>
|
|
75
76
|
<!-- P-2a6d8e74 — image attachment thumbnail chips (paste / drag-drop); hidden until images are attached -->
|
|
76
77
|
<div id="cc-attachments" style="display:none;flex-wrap:wrap;gap:6px;margin-bottom:8px"></div>
|
|
77
78
|
<div style="display:flex;gap:8px">
|
|
78
79
|
<textarea id="cc-input" rows="2" placeholder="Ask anything or give a command... (paste or drop an image)" style="flex:1;padding:8px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:var(--text-md);resize:none;font-family:inherit" onpaste="ccHandlePaste(event)" onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();ccSend()}"></textarea>
|
|
79
80
|
<button onclick="ccSend()" style="background:var(--blue);color:#fff;border:none;border-radius:6px;padding:8px 14px;font-size:var(--text-md);font-weight:600;cursor:pointer;align-self:flex-end">Send</button>
|
|
80
81
|
</div>
|
|
81
|
-
<div id="cc-powered-by" style="font-size:var(--text-
|
|
82
|
+
<div id="cc-powered-by" style="font-size:var(--text-sm);color:var(--muted);margin-top:4px">Full minions context. Enter to send, Shift+Enter for newline. Paste or drop images to attach.</div>
|
|
82
83
|
</div>
|
|
83
84
|
</div>
|
|
84
85
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<div id="notes-list">Loading...</div>
|
|
8
8
|
</section>
|
|
9
9
|
<section>
|
|
10
|
-
<h2>Knowledge Base <span class="count" id="kb-count">0</span> <button class="btn-add" style="margin-left:8px" onclick="openCreateKbModal()">+ New</button><button class="btn-add" style="margin-left:8px" onclick="openMemorySearchModal()">Memory search</button><button id="kb-sweep-btn" onclick="kbSweep()" style="font-size:var(--text-
|
|
10
|
+
<h2>Knowledge Base <span class="count" id="kb-count">0</span> <button class="btn-add" style="margin-left:8px" onclick="openCreateKbModal()">+ New</button><button class="btn-add" style="margin-left:8px" onclick="openMemorySearchModal()">Memory search</button><button id="kb-sweep-btn" onclick="kbSweep()" style="font-size:var(--text-sm);padding:2px 8px;background:var(--surface2);border:1px solid var(--border);color:var(--muted);border-radius:4px;cursor:pointer;margin-left:8px;vertical-align:middle">sweep</button><span id="kb-swept-time" style="font-size:var(--text-sm);color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0;margin-left:8px"></span></h2>
|
|
11
11
|
<div id="kb-sweep-toast" class="cmd-toast cmd-toast-inline" style="margin:6px 0"></div>
|
|
12
12
|
<div class="kb-tabs" id="kb-tabs"></div>
|
|
13
13
|
<div class="kb-list" id="kb-list"><p class="empty">No knowledge entries yet. Notes are classified here after consolidation.</p></div>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<section id="work-items-section" style="overflow:visible">
|
|
2
2
|
<h2>Work Items <span class="count" id="wi-count">0</span>
|
|
3
3
|
<button class="btn-add" style="margin-left:8px" onclick="openCreateWorkItemModal()">+ New</button>
|
|
4
|
-
<button class="pr-pager-btn" style="font-size:var(--text-
|
|
4
|
+
<button class="pr-pager-btn" style="font-size:var(--text-sm);padding:2px 8px;margin-left:4px" onclick="toggleWorkItemArchive()">See Archive</button>
|
|
5
5
|
<span style="font-size:var(--text-sm);color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0">tasks dispatched to agents — auto-created from PRDs or added manually</span>
|
|
6
6
|
</h2>
|
|
7
7
|
<div id="work-items-content"><p class="empty">No work items yet.</p></div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Shared model label for classic and Slim dashboard surfaces.
|
|
2
|
+
var MinionsModelDisplay = {
|
|
3
|
+
label: function(model) {
|
|
4
|
+
var value = model === null || model === undefined ? '' : String(model).trim();
|
|
5
|
+
return value || 'Runtime model not observed yet';
|
|
6
|
+
},
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
if (typeof window !== 'undefined') window.MinionsModelDisplay = MinionsModelDisplay;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Shared watch-loading source for the classic and Slim dashboard surfaces.
|
|
2
|
+
//
|
|
3
|
+
// Watches are SQL-backed: migration 007 moved `engine/watches.json` into the
|
|
4
|
+
// `watches` table, and the SQL-only cutover retired the JSON mirror entirely
|
|
5
|
+
// (see engine/db/index.js#_retiredJsonTargets). The engine mutates watch
|
|
6
|
+
// status/_history through engine/watches-store.js — nothing writes the old
|
|
7
|
+
// file, so serving it over the static `/state/` passthrough 404s forever and
|
|
8
|
+
// any UI reading it renders an empty list. `GET /api/watches` is the canonical
|
|
9
|
+
// read surface.
|
|
10
|
+
//
|
|
11
|
+
// Both dashboards go through this module so the endpoint and the response
|
|
12
|
+
// envelope are described in exactly one place.
|
|
13
|
+
var MinionsWatchesSource = {
|
|
14
|
+
ENDPOINT: '/api/watches',
|
|
15
|
+
|
|
16
|
+
// `GET /api/watches` responds `{ watches: [...] }`. A bare array is accepted
|
|
17
|
+
// defensively so an older/proxied response shape still renders. Returns null
|
|
18
|
+
// (never []) for anything unusable, so callers can tell "no data available"
|
|
19
|
+
// apart from "genuinely zero watches" and keep their last-known slice.
|
|
20
|
+
unwrap: function (body) {
|
|
21
|
+
if (Array.isArray(body)) return body;
|
|
22
|
+
if (body && Array.isArray(body.watches)) return body.watches;
|
|
23
|
+
return null;
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
// Resolves to the watch array, or null when the request fails or the payload
|
|
27
|
+
// is unusable. Never rejects — callers render their previous slice on null.
|
|
28
|
+
fetchWatches: function (fetchImpl) {
|
|
29
|
+
var doFetch = fetchImpl || function (url, opts) { return fetch(url, opts); };
|
|
30
|
+
var unwrap = MinionsWatchesSource.unwrap;
|
|
31
|
+
return Promise.resolve()
|
|
32
|
+
.then(function () {
|
|
33
|
+
return doFetch(MinionsWatchesSource.ENDPOINT, { headers: { Accept: 'application/json' } });
|
|
34
|
+
})
|
|
35
|
+
.then(function (res) {
|
|
36
|
+
if (!res || !res.ok) return null;
|
|
37
|
+
return Promise.resolve(res.json()).then(unwrap, function () { return null; });
|
|
38
|
+
})
|
|
39
|
+
.catch(function () { return null; });
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
if (typeof window !== 'undefined') window.MinionsWatchesSource = MinionsWatchesSource;
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
// Shared internal-distribution welcome popup for the classic and Slim dashboards.
|
|
2
|
+
//
|
|
3
|
+
// W-ms1jgr95000ke749 — the first dashboard launch on an INTERNAL Minions
|
|
4
|
+
// distribution (`@yemishin_microsoft/minions` et al, see engine/distribution.js)
|
|
5
|
+
// shows a one-time modal. Public `@yemi33/minions` installs never see it: the
|
|
6
|
+
// server decides via installed-package metadata and simply answers
|
|
7
|
+
// `{ show: false }`, so this module renders nothing there.
|
|
8
|
+
//
|
|
9
|
+
// Ships in BOTH bundles through dashboard-build.js#DASHBOARD_SHARED_JS — the
|
|
10
|
+
// classic <script> and the Slim IIFE get this one implementation rather than a
|
|
11
|
+
// forked copy each. It therefore runs under strict mode (Slim) and non-strict
|
|
12
|
+
// (classic), declares no implicit globals, and builds its DOM with
|
|
13
|
+
// createElement/textContent (no innerHTML) so it passes the XSS lint gate and
|
|
14
|
+
// can never interpolate server text as markup.
|
|
15
|
+
//
|
|
16
|
+
// Acknowledgement is server-side (POST /api/welcome/ack → SQL small-state keyed
|
|
17
|
+
// on the runtime installation id), NOT localStorage: the popup must stay
|
|
18
|
+
// dismissed across browsers, dashboard restarts and internal-package upgrades,
|
|
19
|
+
// and must reappear only for a genuinely new runtime installation.
|
|
20
|
+
var MinionsWelcomePopup = {
|
|
21
|
+
ENDPOINT: '/api/welcome',
|
|
22
|
+
ACK_ENDPOINT: '/api/welcome/ack',
|
|
23
|
+
MOUNT_ID: 'minions-welcome-popup',
|
|
24
|
+
TITLE_ID: 'minions-welcome-popup-title',
|
|
25
|
+
MESSAGE_ID: 'minions-welcome-popup-message',
|
|
26
|
+
DISMISS_ID: 'minions-welcome-popup-dismiss',
|
|
27
|
+
// Server-authoritative, but mirrored here so the popup still renders its
|
|
28
|
+
// required copy if an older engine omits `message`.
|
|
29
|
+
DEFAULT_MESSAGE: 'Welcome to the internal Minions!',
|
|
30
|
+
|
|
31
|
+
_previousFocus: null,
|
|
32
|
+
_keyHandler: null,
|
|
33
|
+
|
|
34
|
+
// Only an explicit `show: true` from the server opens the popup. Anything
|
|
35
|
+
// else — public distribution, already acknowledged, malformed/absent payload
|
|
36
|
+
// — resolves to false, so a transport hiccup can never pop a public install.
|
|
37
|
+
shouldShow: function (payload) {
|
|
38
|
+
return !!(payload && payload.show === true);
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
// Resolves to the payload, or null on any transport/parse failure. Never
|
|
42
|
+
// rejects: a failed welcome check must not break dashboard boot.
|
|
43
|
+
fetchState: function (fetchImpl) {
|
|
44
|
+
var doFetch = fetchImpl || function (url, opts) { return fetch(url, opts); };
|
|
45
|
+
return Promise.resolve()
|
|
46
|
+
.then(function () {
|
|
47
|
+
return doFetch(MinionsWelcomePopup.ENDPOINT, { headers: { Accept: 'application/json' } });
|
|
48
|
+
})
|
|
49
|
+
.then(function (res) {
|
|
50
|
+
if (!res || !res.ok) return null;
|
|
51
|
+
return Promise.resolve(res.json()).then(function (body) {
|
|
52
|
+
return body && typeof body === 'object' ? body : null;
|
|
53
|
+
}, function () { return null; });
|
|
54
|
+
})
|
|
55
|
+
.catch(function () { return null; });
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
// Fire-and-forget acknowledgement. Resolves true when the server persisted it.
|
|
59
|
+
acknowledge: function (fetchImpl) {
|
|
60
|
+
var doFetch = fetchImpl || function (url, opts) { return fetch(url, opts); };
|
|
61
|
+
return Promise.resolve()
|
|
62
|
+
.then(function () {
|
|
63
|
+
return doFetch(MinionsWelcomePopup.ACK_ENDPOINT, {
|
|
64
|
+
method: 'POST',
|
|
65
|
+
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
|
66
|
+
body: '{}',
|
|
67
|
+
});
|
|
68
|
+
})
|
|
69
|
+
.then(function (res) { return !!(res && res.ok); })
|
|
70
|
+
.catch(function () { return false; });
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
_cardStyle: function () {
|
|
74
|
+
return [
|
|
75
|
+
'background:var(--surface)',
|
|
76
|
+
'border:1px solid var(--blue)',
|
|
77
|
+
'border-radius:var(--radius-lg, 8px)',
|
|
78
|
+
'box-shadow:var(--shadow-md, 0 8px 24px rgba(0,0,0,0.4))',
|
|
79
|
+
'width:min(440px, calc(100vw - 32px))',
|
|
80
|
+
'padding:22px 24px',
|
|
81
|
+
'color:var(--text)',
|
|
82
|
+
'font-size:var(--text-md)',
|
|
83
|
+
'line-height:1.55',
|
|
84
|
+
'text-align:left',
|
|
85
|
+
].join(';');
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
_backdropStyle: function () {
|
|
89
|
+
return [
|
|
90
|
+
'position:fixed',
|
|
91
|
+
'inset:0',
|
|
92
|
+
'background:rgba(0,0,0,0.75)',
|
|
93
|
+
'z-index:600',
|
|
94
|
+
'display:flex',
|
|
95
|
+
'align-items:center',
|
|
96
|
+
'justify-content:center',
|
|
97
|
+
].join(';');
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
// Build (but do not mount) the dialog. `doc` is injectable for tests.
|
|
101
|
+
build: function (payload, doc) {
|
|
102
|
+
var d = doc || document;
|
|
103
|
+
var message = (payload && typeof payload.message === 'string' && payload.message.trim())
|
|
104
|
+
? payload.message
|
|
105
|
+
: MinionsWelcomePopup.DEFAULT_MESSAGE;
|
|
106
|
+
|
|
107
|
+
var backdrop = d.createElement('div');
|
|
108
|
+
backdrop.id = MinionsWelcomePopup.MOUNT_ID;
|
|
109
|
+
backdrop.className = 'modal-bg minions-welcome-bg';
|
|
110
|
+
backdrop.setAttribute('style', MinionsWelcomePopup._backdropStyle());
|
|
111
|
+
|
|
112
|
+
var card = d.createElement('div');
|
|
113
|
+
card.className = 'modal minions-welcome-card';
|
|
114
|
+
card.setAttribute('role', 'dialog');
|
|
115
|
+
card.setAttribute('aria-modal', 'true');
|
|
116
|
+
card.setAttribute('aria-labelledby', MinionsWelcomePopup.TITLE_ID);
|
|
117
|
+
card.setAttribute('aria-describedby', MinionsWelcomePopup.MESSAGE_ID);
|
|
118
|
+
card.setAttribute('style', MinionsWelcomePopup._cardStyle());
|
|
119
|
+
|
|
120
|
+
// The primary message IS the heading — the required copy must be the first
|
|
121
|
+
// thing both a sighted user and a screen reader receive.
|
|
122
|
+
var title = d.createElement('h2');
|
|
123
|
+
title.id = MinionsWelcomePopup.TITLE_ID;
|
|
124
|
+
title.className = 'minions-welcome-title';
|
|
125
|
+
title.setAttribute('style', 'margin:0;font-size:var(--text-xl);font-weight:700;color:var(--blue)');
|
|
126
|
+
title.textContent = message;
|
|
127
|
+
card.appendChild(title);
|
|
128
|
+
|
|
129
|
+
var body = d.createElement('p');
|
|
130
|
+
body.id = MinionsWelcomePopup.MESSAGE_ID;
|
|
131
|
+
body.className = 'minions-welcome-message';
|
|
132
|
+
body.setAttribute('style', 'margin:10px 0 0;color:var(--muted)');
|
|
133
|
+
body.textContent = 'This is the internal build of Minions. Everything works the same as the public release — you are just on the internal distribution channel.';
|
|
134
|
+
card.appendChild(body);
|
|
135
|
+
|
|
136
|
+
var pkg = payload && payload.distribution && payload.distribution.packageName;
|
|
137
|
+
if (typeof pkg === 'string' && pkg) {
|
|
138
|
+
var pkgLine = d.createElement('p');
|
|
139
|
+
pkgLine.className = 'minions-welcome-package';
|
|
140
|
+
pkgLine.setAttribute('style', 'margin:8px 0 0;color:var(--muted);font-size:var(--text-sm)');
|
|
141
|
+
var pkgLabel = d.createElement('span');
|
|
142
|
+
pkgLabel.textContent = 'Package: ';
|
|
143
|
+
var pkgCode = d.createElement('code');
|
|
144
|
+
pkgCode.textContent = pkg;
|
|
145
|
+
pkgLine.appendChild(pkgLabel);
|
|
146
|
+
pkgLine.appendChild(pkgCode);
|
|
147
|
+
card.appendChild(pkgLine);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
var actions = d.createElement('div');
|
|
151
|
+
actions.setAttribute('style', 'display:flex;justify-content:flex-end;margin-top:18px');
|
|
152
|
+
var dismiss = d.createElement('button');
|
|
153
|
+
dismiss.id = MinionsWelcomePopup.DISMISS_ID;
|
|
154
|
+
dismiss.type = 'button';
|
|
155
|
+
dismiss.className = 'btn btn-primary';
|
|
156
|
+
dismiss.setAttribute('style', 'padding:7px 18px;font-weight:600;cursor:pointer');
|
|
157
|
+
dismiss.textContent = 'Get started';
|
|
158
|
+
actions.appendChild(dismiss);
|
|
159
|
+
card.appendChild(actions);
|
|
160
|
+
|
|
161
|
+
backdrop.appendChild(card);
|
|
162
|
+
return backdrop;
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
isOpen: function (doc) {
|
|
166
|
+
var d = doc || document;
|
|
167
|
+
return !!d.getElementById(MinionsWelcomePopup.MOUNT_ID);
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
// Remove the dialog and hand focus back to whatever had it before opening.
|
|
171
|
+
close: function (opts) {
|
|
172
|
+
var d = (opts && opts.document) || document;
|
|
173
|
+
var node = d.getElementById(MinionsWelcomePopup.MOUNT_ID);
|
|
174
|
+
if (node && node.parentNode) node.parentNode.removeChild(node);
|
|
175
|
+
if (MinionsWelcomePopup._keyHandler) {
|
|
176
|
+
try { d.removeEventListener('keydown', MinionsWelcomePopup._keyHandler, true); } catch (e) { /* detached doc */ }
|
|
177
|
+
MinionsWelcomePopup._keyHandler = null;
|
|
178
|
+
}
|
|
179
|
+
var prev = MinionsWelcomePopup._previousFocus;
|
|
180
|
+
MinionsWelcomePopup._previousFocus = null;
|
|
181
|
+
if (prev && typeof prev.focus === 'function') {
|
|
182
|
+
try { prev.focus(); } catch (e) { /* element may be gone */ }
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
// Close + persist the acknowledgement. Closing is optimistic: the operator
|
|
187
|
+
// should never be blocked by a failing ack round-trip.
|
|
188
|
+
dismiss: function (opts) {
|
|
189
|
+
MinionsWelcomePopup.close(opts);
|
|
190
|
+
return MinionsWelcomePopup.acknowledge(opts && opts.fetchImpl);
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
// Mount the dialog, wire dismissal (button, Escape, backdrop click), trap Tab
|
|
194
|
+
// inside the card, and move focus to the primary action.
|
|
195
|
+
open: function (payload, opts) {
|
|
196
|
+
var d = (opts && opts.document) || document;
|
|
197
|
+
if (MinionsWelcomePopup.isOpen(d)) return d.getElementById(MinionsWelcomePopup.MOUNT_ID);
|
|
198
|
+
var node = MinionsWelcomePopup.build(payload, d);
|
|
199
|
+
MinionsWelcomePopup._previousFocus = d.activeElement || null;
|
|
200
|
+
(d.body || d.documentElement).appendChild(node);
|
|
201
|
+
|
|
202
|
+
var dismissBtn = d.getElementById(MinionsWelcomePopup.DISMISS_ID);
|
|
203
|
+
function doDismiss() { MinionsWelcomePopup.dismiss(opts); }
|
|
204
|
+
if (dismissBtn) dismissBtn.addEventListener('click', doDismiss);
|
|
205
|
+
node.addEventListener('click', function (ev) { if (ev.target === node) doDismiss(); });
|
|
206
|
+
|
|
207
|
+
MinionsWelcomePopup._keyHandler = function (ev) {
|
|
208
|
+
if (!MinionsWelcomePopup.isOpen(d)) return;
|
|
209
|
+
if (ev.key === 'Escape') {
|
|
210
|
+
if (typeof ev.preventDefault === 'function') ev.preventDefault();
|
|
211
|
+
doDismiss();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (ev.key === 'Tab') {
|
|
215
|
+
// Single focusable control — keep focus parked on it either direction.
|
|
216
|
+
if (typeof ev.preventDefault === 'function') ev.preventDefault();
|
|
217
|
+
if (dismissBtn && typeof dismissBtn.focus === 'function') {
|
|
218
|
+
try { dismissBtn.focus(); } catch (e) { /* ignore */ }
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
d.addEventListener('keydown', MinionsWelcomePopup._keyHandler, true);
|
|
223
|
+
|
|
224
|
+
if (dismissBtn && typeof dismissBtn.focus === 'function') {
|
|
225
|
+
try { dismissBtn.focus(); } catch (e) { /* ignore */ }
|
|
226
|
+
}
|
|
227
|
+
return node;
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
// Ask the server, then open when (and only when) it says so.
|
|
231
|
+
check: function (opts) {
|
|
232
|
+
return MinionsWelcomePopup.fetchState(opts && opts.fetchImpl).then(function (payload) {
|
|
233
|
+
if (!MinionsWelcomePopup.shouldShow(payload)) return null;
|
|
234
|
+
return MinionsWelcomePopup.open(payload, opts);
|
|
235
|
+
});
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
// Idempotent boot hook — runs at most once per page load in either surface.
|
|
239
|
+
init: function (opts) {
|
|
240
|
+
var w = (opts && opts.window) || (typeof window !== 'undefined' ? window : null);
|
|
241
|
+
if (!w) return Promise.resolve(null);
|
|
242
|
+
if (w.__minionsWelcomePopupInit) return Promise.resolve(null);
|
|
243
|
+
w.__minionsWelcomePopupInit = true;
|
|
244
|
+
return MinionsWelcomePopup.check(opts);
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
if (typeof window !== 'undefined') {
|
|
249
|
+
window.MinionsWelcomePopup = MinionsWelcomePopup;
|
|
250
|
+
// Both bundles inline their scripts at the end of <body>, so the DOM is
|
|
251
|
+
// normally parsed already; the readyState guard covers deferred/async loads.
|
|
252
|
+
if (typeof document !== 'undefined') {
|
|
253
|
+
if (document.readyState === 'loading') {
|
|
254
|
+
document.addEventListener('DOMContentLoaded', function () { MinionsWelcomePopup.init(); });
|
|
255
|
+
} else {
|
|
256
|
+
MinionsWelcomePopup.init();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
package/dashboard/slim/body.html
CHANGED
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
<span class="context-label">Working in</span>
|
|
36
36
|
<span id="chat-context-controls"></span>
|
|
37
37
|
</div>
|
|
38
|
+
<div id="chat-suggestions-host"></div>
|
|
38
39
|
<div class="chat-input-wrap">
|
|
39
40
|
<textarea
|
|
40
41
|
id="chat-input"
|
|
@@ -63,7 +64,7 @@
|
|
|
63
64
|
<!-- STATUS — cockpit tiles. Lit when their indicator is non-zero. -->
|
|
64
65
|
<div class="panel panel-status">
|
|
65
66
|
<div class="panel-header">
|
|
66
|
-
|
|
67
|
+
Status
|
|
67
68
|
<span class="panel-sub" id="cockpit-stamp">refreshing…</span>
|
|
68
69
|
</div>
|
|
69
70
|
<div class="panel-body">
|
|
@@ -110,10 +111,10 @@
|
|
|
110
111
|
<div class="cockpit-value dim">0</div>
|
|
111
112
|
<div class="cockpit-detail">no open PRs</div>
|
|
112
113
|
</div>
|
|
113
|
-
<div class="cockpit-tile" data-tile="
|
|
114
|
-
<div class="cockpit-label"><span class="cockpit-dot"></span>
|
|
114
|
+
<div class="cockpit-tile" data-tile="automation">
|
|
115
|
+
<div class="cockpit-label"><span class="cockpit-dot"></span> Automation</div>
|
|
115
116
|
<div class="cockpit-value dim">0</div>
|
|
116
|
-
<div class="cockpit-detail">no
|
|
117
|
+
<div class="cockpit-detail">no automation set</div>
|
|
117
118
|
</div>
|
|
118
119
|
<div class="cockpit-tile" data-tile="knowledge">
|
|
119
120
|
<button id="slim-tile-pin-chip" class="linkpr-chip on-tile" type="button" title="Pin content">+ Pin</button>
|
|
@@ -161,7 +162,7 @@
|
|
|
161
162
|
<p>A focused, single-screen cockpit for your Minions team. Here’s what’s new:</p>
|
|
162
163
|
<ul class="welcome-list">
|
|
163
164
|
<li><strong>Command Center front and center</strong> — chat with your team from the main panel, with multi-tab conversations that carry over from the classic dashboard.</li>
|
|
164
|
-
<li><strong>Live status tiles</strong> — engine, dispatches, queue, PRs and
|
|
165
|
+
<li><strong>Live status tiles</strong> — engine, dispatches, queue, PRs and automation at a glance. Click any tile for details.</li>
|
|
165
166
|
<li><strong>Your team</strong> — every agent as a card with live status.</li>
|
|
166
167
|
<li><strong>Recent activity</strong> — a running feed of completions and pull requests.</li>
|
|
167
168
|
</ul>
|
|
@@ -426,4 +427,3 @@
|
|
|
426
427
|
<!-- Floating toast target for the reused panel's charter-save / steering
|
|
427
428
|
feedback (showToast in panel-bootstrap.js routes here). -->
|
|
428
429
|
<div class="cmd-toast" id="cmd-toast" role="status" aria-live="polite"></div>
|
|
429
|
-
|
|
@@ -116,6 +116,7 @@
|
|
|
116
116
|
var tabId = _ensureActiveTab().id;
|
|
117
117
|
|
|
118
118
|
var msgsEl = document.getElementById('chat-messages');
|
|
119
|
+
var suggestionsEl = document.getElementById('chat-suggestions-host');
|
|
119
120
|
var inputEl = document.getElementById('chat-input');
|
|
120
121
|
var sendBtn = document.getElementById('chat-send');
|
|
121
122
|
var stopBtn = document.getElementById('chat-stop');
|
|
@@ -307,7 +308,7 @@
|
|
|
307
308
|
}
|
|
308
309
|
|
|
309
310
|
function clearEmpty() {
|
|
310
|
-
clearCcSuggestedPrompts(
|
|
311
|
+
clearCcSuggestedPrompts(suggestionsEl);
|
|
311
312
|
var empty = msgsEl.querySelector('.chat-empty');
|
|
312
313
|
if (empty) empty.remove();
|
|
313
314
|
}
|
|
@@ -626,9 +627,10 @@
|
|
|
626
627
|
|
|
627
628
|
function rerenderHistory() {
|
|
628
629
|
msgsEl.innerHTML = '';
|
|
630
|
+
clearCcSuggestedPrompts(suggestionsEl);
|
|
629
631
|
queueEl = null; // detached by the innerHTML reset; renderQueue rebuilds it
|
|
630
632
|
if (!messages.length) {
|
|
631
|
-
renderCcSuggestedPrompts(
|
|
633
|
+
renderCcSuggestedPrompts(suggestionsEl, function(prompt) {
|
|
632
634
|
inputEl.value = prompt;
|
|
633
635
|
sendMessage();
|
|
634
636
|
});
|
|
@@ -506,8 +506,15 @@
|
|
|
506
506
|
}
|
|
507
507
|
// Resolve to `fallback` instead of rejecting, so one transient endpoint
|
|
508
508
|
// error doesn't blank the whole cockpit — we keep the last-known slice.
|
|
509
|
-
|
|
510
|
-
|
|
509
|
+
// `map` optionally unwraps an enveloped payload (e.g. /api/watches returns
|
|
510
|
+
// `{ watches: [...] }`); a null mapping is treated as unusable data and also
|
|
511
|
+
// falls back to the last-known slice.
|
|
512
|
+
function _slimSettle(url, fallback, map) {
|
|
513
|
+
return _slimFetchJson(url).then(function(v) {
|
|
514
|
+
if (!map) return v;
|
|
515
|
+
var mapped = map(v);
|
|
516
|
+
return mapped == null ? fallback : mapped;
|
|
517
|
+
}, function() { return fallback; });
|
|
511
518
|
}
|
|
512
519
|
|
|
513
520
|
async function pollStatusOnce() {
|
|
@@ -522,7 +529,7 @@
|
|
|
522
529
|
_slimSettle('/api/agents', prev.agents),
|
|
523
530
|
_slimSettle('/api/pull-requests', prev.pullRequests),
|
|
524
531
|
_slimSettle('/api/pinned', prev.pinned),
|
|
525
|
-
_slimSettle(
|
|
532
|
+
_slimSettle(MinionsWatchesSource.ENDPOINT, prev.watches, MinionsWatchesSource.unwrap),
|
|
526
533
|
_slimSettle('/api/work-items', prev.workItems),
|
|
527
534
|
]);
|
|
528
535
|
var data = results[0] || {};
|
|
@@ -180,18 +180,85 @@
|
|
|
180
180
|
body.appendChild(frame);
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
183
|
+
// Automation tile body — a tabbed composite (Watches · Schedules · Pipelines)
|
|
184
|
+
// that reuses the LITERAL classic dashboard screens (/watches, /schedule,
|
|
185
|
+
// /pipelines) instead of a slim-specific list, so there is one Automation UI,
|
|
186
|
+
// not two (W-mrz0x2a400030f90 / "reuse, don't fork" — mirrors renderPlansBody
|
|
187
|
+
// for the Plans tile and renderPrsBody for the PRs tile). Each tab embeds the
|
|
188
|
+
// real classic screen in an iframe with the chrome-off ?embed=1 mode; the
|
|
189
|
+
// iframed page IS the classic screen — full create/edit/pause/run + row
|
|
190
|
+
// actions, backed by the same /api/{watches,schedules,pipelines} endpoints —
|
|
191
|
+
// with zero duplicated rendering logic. Classic is reachable at each route
|
|
192
|
+
// even with slim-ux ON (only / is taken over). Iframes are LAZY: each tab's
|
|
193
|
+
// frame is mounted only on first activation (default Watches), so opening the
|
|
194
|
+
// modal never triple-loads all three screens. Built with createElement (no
|
|
195
|
+
// innerHTML) to satisfy the dashboard no-unsanitized lint gate; tab buttons
|
|
196
|
+
// reuse the shared .kn-tab pill primitive and carry role="tab"/aria-selected
|
|
197
|
+
// for keyboard/AT parity with the Knowledge modal tabs.
|
|
198
|
+
function renderAutomationTileBody(body) {
|
|
199
|
+
var TABS = [
|
|
200
|
+
{ key: 'watches', label: 'Watches', src: '/watches?embed=1' },
|
|
201
|
+
{ key: 'schedule', label: 'Schedules', src: '/schedule?embed=1' },
|
|
202
|
+
{ key: 'pipelines', label: 'Pipelines', src: '/pipelines?embed=1' },
|
|
203
|
+
];
|
|
204
|
+
var tablist = document.createElement('div');
|
|
205
|
+
tablist.className = 'kn-tabs slim-automation-tabs';
|
|
206
|
+
tablist.setAttribute('role', 'tablist');
|
|
207
|
+
tablist.setAttribute('aria-label', 'Automation surfaces');
|
|
208
|
+
|
|
209
|
+
var panes = document.createElement('div');
|
|
210
|
+
panes.className = 'slim-automation-panes';
|
|
211
|
+
|
|
212
|
+
var btns = {};
|
|
213
|
+
var panels = {};
|
|
214
|
+
var frames = {};
|
|
215
|
+
|
|
216
|
+
function activate(key) {
|
|
217
|
+
TABS.forEach(function(t) {
|
|
218
|
+
var isActive = t.key === key;
|
|
219
|
+
var btn = btns[t.key];
|
|
220
|
+
btn.classList.toggle('active', isActive);
|
|
221
|
+
btn.setAttribute('aria-selected', isActive ? 'true' : 'false');
|
|
222
|
+
btn.setAttribute('tabindex', isActive ? '0' : '-1');
|
|
223
|
+
var panel = panels[t.key];
|
|
224
|
+
panel.hidden = !isActive;
|
|
225
|
+
// Lazy-mount: only build the iframe on first activation of its tab.
|
|
226
|
+
if (isActive && !frames[t.key]) {
|
|
227
|
+
var frame = document.createElement('iframe');
|
|
228
|
+
frame.className = 'slim-automation-embed';
|
|
229
|
+
frame.src = t.src;
|
|
230
|
+
frame.title = t.label;
|
|
231
|
+
panel.appendChild(frame);
|
|
232
|
+
frames[t.key] = frame;
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
TABS.forEach(function(t) {
|
|
238
|
+
var btn = document.createElement('button');
|
|
239
|
+
btn.type = 'button';
|
|
240
|
+
btn.className = 'kn-tab';
|
|
241
|
+
btn.id = 'slim-automation-tab-' + t.key;
|
|
242
|
+
btn.textContent = t.label;
|
|
243
|
+
btn.setAttribute('role', 'tab');
|
|
244
|
+
btn.setAttribute('aria-controls', 'slim-automation-panel-' + t.key);
|
|
245
|
+
btn.addEventListener('click', function() { activate(t.key); });
|
|
246
|
+
btns[t.key] = btn;
|
|
247
|
+
tablist.appendChild(btn);
|
|
248
|
+
|
|
249
|
+
var panel = document.createElement('div');
|
|
250
|
+
panel.className = 'slim-automation-panel';
|
|
251
|
+
panel.id = 'slim-automation-panel-' + t.key;
|
|
252
|
+
panel.setAttribute('role', 'tabpanel');
|
|
253
|
+
panel.setAttribute('aria-labelledby', btn.id);
|
|
254
|
+
panel.hidden = true;
|
|
255
|
+
panels[t.key] = panel;
|
|
256
|
+
panes.appendChild(panel);
|
|
194
257
|
});
|
|
258
|
+
|
|
259
|
+
body.appendChild(tablist);
|
|
260
|
+
body.appendChild(panes);
|
|
261
|
+
activate('watches'); // default to the Watches tab
|
|
195
262
|
}
|
|
196
263
|
|
|
197
264
|
// tile key -> { title, render }. Adding a tile is a one-line table edit.
|
|
@@ -201,7 +268,7 @@
|
|
|
201
268
|
queued: { title: 'Queued work', render: function(body) { renderQueuedWorkBody(body); } },
|
|
202
269
|
plans: { title: 'Plans', render: function(body) { renderPlansBody(body); } },
|
|
203
270
|
prs: { title: 'Pull requests', render: function(body) { renderPrsBody(body); } },
|
|
204
|
-
|
|
271
|
+
automation: { title: 'Automation', render: function(body) { renderAutomationTileBody(body); } },
|
|
205
272
|
};
|
|
206
273
|
|
|
207
274
|
// Populate + open the cockpit-tile detail modal from the latest status
|
|
@@ -221,14 +288,16 @@
|
|
|
221
288
|
// The "+ Link PR" header chip only applies to the PR list.
|
|
222
289
|
var headerChip = document.getElementById('slim-tile-modal-linkpr');
|
|
223
290
|
if (headerChip) headerChip.style.display = (key === 'prs') ? '' : 'none';
|
|
224
|
-
// The engine, queued, plans +
|
|
225
|
-
// (/engine, /work, /plans, /prs
|
|
226
|
-
//
|
|
227
|
-
// shared wide-modal
|
|
291
|
+
// The engine, queued, plans, prs + automation tiles embed a full classic
|
|
292
|
+
// screen (/engine, /work, /plans, /prs, and the /watches·/schedule·
|
|
293
|
+
// /pipelines tabs) in an iframe — widen the modal + drop the body padding so
|
|
294
|
+
// the embedded screen gets real estate. All reuse the shared wide-modal
|
|
295
|
+
// treatment.
|
|
228
296
|
modal.classList.toggle('tile-modal--engine', key === 'engine');
|
|
229
297
|
modal.classList.toggle('tile-modal--work', key === 'queued');
|
|
230
298
|
modal.classList.toggle('tile-modal--plans', key === 'plans');
|
|
231
299
|
modal.classList.toggle('tile-modal--prs', key === 'prs');
|
|
300
|
+
modal.classList.toggle('tile-modal--automation', key === 'automation');
|
|
232
301
|
view.render(body, lastStatusData || {});
|
|
233
302
|
modal.classList.add('open');
|
|
234
303
|
}
|