@yemi33/minions 0.1.2411 → 0.1.2413

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.
@@ -2388,4 +2388,28 @@ if (document.readyState === 'loading') {
2388
2388
  ccInitResize();
2389
2389
  }
2390
2390
 
2391
+ // W-mrz5yjdw00052b6e — Esc closes the Command Center drawer. Routes through
2392
+ // toggleCommandCenter() so the open/close path stays the single source of truth
2393
+ // (same path as the ✕ button and the overlay click). Bubble phase, and guarded
2394
+ // so it only fires when the drawer is actually open — Esc is not stolen when CC
2395
+ // is hidden. If a modal (#modal.open) or the QA review modal is layered on top,
2396
+ // we early-out so the topmost overlay closes first (modal.js / qa.js own that
2397
+ // Esc). Closing while mid-typing in cc-input IS desired, so we do NOT exempt
2398
+ // TEXTAREA here — but the top command bar's @/# autocomplete popup gets first
2399
+ // dibs on Esc (first Esc dismisses the typeahead, second closes the panel).
2400
+ if (typeof document !== 'undefined' && typeof document.addEventListener === 'function') {
2401
+ document.addEventListener('keydown', function(ev) {
2402
+ if (ev.key !== 'Escape' && ev.keyCode !== 27) return;
2403
+ if (!_ccOpen) return;
2404
+ // Let a modal / QA review modal layered on top close first.
2405
+ if (typeof _qaModalState !== 'undefined' && _qaModalState) return;
2406
+ var modalEl = document.getElementById('modal');
2407
+ if (modalEl && modalEl.classList.contains('open')) return;
2408
+ // First Esc dismisses the command-bar autocomplete popup, not the drawer.
2409
+ var popup = document.getElementById('cmd-mention-popup');
2410
+ if (popup && popup.classList.contains('visible')) return;
2411
+ toggleCommandCenter();
2412
+ });
2413
+ }
2414
+
2391
2415
  window.MinionsCC = { toggleCommandCenter, ccNewSession, ccNewTab, ccSwitchTab, ccCloseTab, ccRenderTabBar, ccRestoreMessages, ccSaveState, ccUpdateSessionIndicator, ccAddMessage, ccSend, ccAbort, ccExecuteAction, ccPrActionFollowup, ccAddImageFiles, ccRemoveAttachment, ccHandlePaste, ccHandleDragOver, ccHandleDragLeave, ccHandleDrop };
@@ -253,6 +253,29 @@ async function openSettings() {
253
253
  // preferences (font-size scale today; reserved for future theme picks).
254
254
  // Kept narrow + featured near the rail top so the setting is discoverable
255
255
  // rather than buried under generic Engine headers.
256
+ // W-mryz1okr00062b6b — the slim-ux enable/disable control lives in Appearance
257
+ // (a dashboard-wide visual preference) rather than the generic Feature Flags
258
+ // list. It reuses the EXISTING toggle wiring: rendering a checkbox tagged with
259
+ // the data-feature-toggle attribute set to slim-ux means the delegated
260
+ // change-handler below (querySelectorAll('input[data-feature-toggle]'))
261
+ // persists it via POST /api/features/toggle — no new listener, no new
262
+ // endpoint. If the flag is missing from the registry response we hide the
263
+ // control rather than render a broken toggle.
264
+ const slimUxFeature = featuresList.find(function(f) { return f.id === 'slim-ux'; });
265
+ const slimUxEnabled = slimUxFeature
266
+ ? (slimUxFeature.enabled != null ? !!slimUxFeature.enabled : !!slimUxFeature.default)
267
+ : false;
268
+ const slimUxControl = slimUxFeature
269
+ ? '<div data-search="slim ux appearance experimental cockpit">' +
270
+ '<label style="display:flex;align-items:flex-start;gap:8px;cursor:pointer">' +
271
+ '<input type="checkbox" data-feature-toggle="slim-ux"' + (slimUxEnabled ? ' checked' : '') + ' style="margin-top:3px;accent-color:var(--blue);width:16px;height:16px;cursor:pointer;flex-shrink:0">' +
272
+ '<span style="flex:1">' +
273
+ '<span style="font-size:var(--text-md);color:var(--text);display:block">Slim UX</span>' +
274
+ '<span style="font-size:var(--text-xs);color:var(--muted);display:block;margin-top:1px">Experimental focused single-screen cockpit. Persists immediately.</span>' +
275
+ '</span>' +
276
+ '</label>' +
277
+ '</div>'
278
+ : '';
256
279
  const paneAppearance =
257
280
  '<h3>Appearance</h3>' +
258
281
  '<div class="settings-pane-sub">Dashboard-wide visual preferences. Persisted in browser localStorage + server-side settings so they survive a reload from a cold cache.</div>' +
@@ -270,6 +293,7 @@ async function openSettings() {
270
293
  '</select>' +
271
294
  '<div style="font-size:var(--text-xs);color:var(--muted);margin-top:1px">Scales the whole dashboard. Persisted in browser + server.</div>' +
272
295
  '</div>' +
296
+ slimUxControl +
273
297
  '</div>';
274
298
 
275
299
  // W-mrtdw70o000sb227 — Projects tab now shows ONE project's config at a time,
@@ -605,17 +629,21 @@ async function openSettings() {
605
629
  '</div>';
606
630
 
607
631
  // Toggles persist immediately via POST /api/features/toggle — no Save needed.
632
+ // W-mryz1okr00062b6b — slim-ux is surfaced in the Appearance pane instead, so
633
+ // exclude it here (display-only; the /api/features payload + registry are
634
+ // untouched). The "(N registered)" count below reflects what this list shows.
635
+ const displayFeatures = featuresList.filter(function(f) { return f.id !== 'slim-ux'; });
608
636
  const paneFeatures =
609
637
  '<h3>Feature Flags</h3>' +
610
638
  '<div class="settings-pane-sub">In-progress UX or behavior gates. Toggles persist immediately. Registry: <code>engine/features.js</code>. Env override: <code>MINIONS_FEATURE_&lt;NAME&gt;=1</code>.</div>' +
611
639
  '<details id="settings-features-details" open style="border:1px solid var(--border);border-radius:6px;padding:12px">' +
612
640
  '<summary style="cursor:pointer;font-size:var(--text-md);color:var(--text);user-select:none;margin-bottom:8px">Show experimental flags ' +
613
- '<span style="font-size:var(--text-sm);color:var(--muted)">(' + featuresList.length + ' registered)</span>' +
641
+ '<span style="font-size:var(--text-sm);color:var(--muted)">(' + displayFeatures.length + ' registered)</span>' +
614
642
  '</summary>' +
615
- (featuresList.length === 0
643
+ (displayFeatures.length === 0
616
644
  ? '<div style="font-size:var(--text-base);color:var(--muted);padding:12px;border:1px dashed var(--border);border-radius:4px;text-align:center">No experimental features registered. Add entries to <code>engine/features.js</code> to gate new work.</div>'
617
645
  : '<div style="display:flex;flex-direction:column;gap:8px">' +
618
- featuresList.map(function(f) {
646
+ displayFeatures.map(function(f) {
619
647
  const checked = f.enabled ? ' checked' : '';
620
648
  const expiredBadge = f.expired
621
649
  ? ' <span style="font-size:var(--text-xs);padding:1px 5px;background:rgba(220,80,80,0.15);color:var(--red);border-radius:3px;margin-left:4px">EXPIRED</span>'
@@ -405,3 +405,25 @@
405
405
  </div>
406
406
  </div>
407
407
 
408
+ <!-- Reused CLASSIC agent detail slide-in panel (W-mrz11djx00050594). Opened by
409
+ clicking a Team member card; populated by the reused dashboard/js panel
410
+ modules (openAgentDetail → renderDetailTabs/renderDetailContent) exposed on
411
+ window.MinionsAgents. Structure mirrors dashboard/layout.html so the shared
412
+ JS + CSS resolve unchanged. -->
413
+ <div class="detail-overlay" id="detail-overlay" onclick="closeDetail()"></div>
414
+ <div class="detail-panel" id="detail-panel">
415
+ <div class="detail-header">
416
+ <h3 id="detail-agent-name">—</h3>
417
+ <button class="detail-close" onclick="closeDetail()">ESC / Close</button>
418
+ </div>
419
+ <div class="status-line" id="detail-status-line">—</div>
420
+ <div class="detail-tabs" id="detail-tabs"></div>
421
+ <div class="detail-body">
422
+ <div class="detail-content" id="detail-content"></div>
423
+ </div>
424
+ </div>
425
+
426
+ <!-- Floating toast target for the reused panel's charter-save / steering
427
+ feedback (showToast in panel-bootstrap.js routes here). -->
428
+ <div class="cmd-toast" id="cmd-toast" role="status" aria-live="polite"></div>
429
+
@@ -23,6 +23,12 @@
23
23
  function renderMembers(agents) {
24
24
  var grid = document.getElementById('member-grid');
25
25
  if (!grid) return;
26
+ // Feed the reused CLASSIC detail panel (W-mrz11djx00050594): its
27
+ // openAgentDetail(id) does agentData.find(...), so keep the global
28
+ // agentData (declared in slim/panel-bootstrap.js) in sync with the live
29
+ // /api/status agents. Set before the unchanged-early-return so it stays
30
+ // current even on no-op polls.
31
+ try { window.agentData = agents; } catch { /* non-fatal */ }
26
32
  var json = JSON.stringify(agents);
27
33
  if (json === _lastAgentsJson) return;
28
34
  _lastAgentsJson = json;
@@ -43,9 +49,9 @@
43
49
  card.setAttribute('tabindex', '0');
44
50
  card.title = (a.name || a.id) + (a.role ? ' — ' + a.role : '');
45
51
  // The clicked agent object is captured directly — no id->lookup round-trip.
46
- card.addEventListener('click', function() { openAgentDetail(a); });
52
+ card.addEventListener('click', function() { openAgentDetailPanel(a); });
47
53
  card.addEventListener('keydown', function(ev) {
48
- if (ev.key === 'Enter' || ev.key === ' ') { ev.preventDefault(); openAgentDetail(a); }
54
+ if (ev.key === 'Enter' || ev.key === ' ') { ev.preventDefault(); openAgentDetailPanel(a); }
49
55
  });
50
56
  // Hover (and keyboard focus, for tabindex=0 parity) reveals the inline
51
57
  // details bar under the grid without opening the click->modal. Listeners
@@ -219,8 +225,29 @@
219
225
  body.appendChild(row);
220
226
  }
221
227
 
228
+ // Route an agent-card click to the CLASSIC rich detail slide-in panel
229
+ // (W-mrz11djx00050594) — tabs + live-output stream + charter editor + full
230
+ // status — reused verbatim from the classic dashboard and exposed on
231
+ // window.MinionsAgents by dashboard/js/render-agents.js (injected into the
232
+ // slim document as a separate global <script>; see dashboard-build.js).
233
+ // Falls back to the slim mini-modal only if that panel bundle is genuinely
234
+ // unavailable, so a build/regression that drops the panel still shows detail.
235
+ function openAgentDetailPanel(a) {
236
+ if (!a || !a.id) return;
237
+ var MA = window.MinionsAgents;
238
+ if (MA && typeof MA.openAgentDetail === 'function') {
239
+ // openAgentDetail(id) resolves the agent via the global agentData, which
240
+ // renderMembers keeps in sync; pass the id (classic takes an id, not the
241
+ // whole object).
242
+ MA.openAgentDetail(a.id);
243
+ return;
244
+ }
245
+ openAgentDetail(a); // graceful fallback: slim mini-modal
246
+ }
247
+
222
248
  // Populate + open the agent detail modal for the given agent object (passed
223
- // straight from the clicked card).
249
+ // straight from the clicked card). Retained as the fallback for
250
+ // openAgentDetailPanel when the reused classic panel is unavailable.
224
251
  function openAgentDetail(a) {
225
252
  var modal = document.getElementById('slim-agent-modal');
226
253
  var body = document.getElementById('slim-agent-body');
@@ -18,5 +18,13 @@
18
18
  /* __JS__ */
19
19
  })();
20
20
  </script>
21
+ <!-- Reused CLASSIC agent detail slide-in panel (W-mrz11djx00050594). Runs at
22
+ GLOBAL, non-strict scope — the same scope the classic dashboard uses — so
23
+ the reused dashboard/js panel modules behave identically. Kept separate from
24
+ (and after) the slim IIFE above; it exposes window.MinionsAgents.openAgentDetail(id),
25
+ which members.js calls on card click. -->
26
+ <script>
27
+ /* __PANEL_JS__ */
28
+ </script>
21
29
  </body>
22
30
  </html>
@@ -0,0 +1,83 @@
1
+ // dashboard/slim/panel-bootstrap.js — glue for reusing the CLASSIC agent detail
2
+ // slide-in panel inside the Slim-UX document (W-mrz11djx00050594).
3
+ //
4
+ // The classic panel modules (utils, render-utils, charter-editor, live-stream,
5
+ // render-agents, detail-panel) are the SAME real files the classic dashboard
6
+ // ships — dashboard-build.js concatenates them after this bootstrap into one
7
+ // GLOBAL, non-strict <script> so they run in the exact scope they were written
8
+ // for (unlike slim's own strict IIFE). Those files read a handful of primitives
9
+ // that normally live in classic's state.js / command-parser.js, which slim does
10
+ // NOT bundle. Rather than fork the panel, we wire up just those primitives here:
11
+ //
12
+ // - agentData / currentAgentId / currentTab (classic panel module state)
13
+ // - safeFetch (state.js — API fetch w/ timeout)
14
+ // - showToast (command-parser.js — toast feedback)
15
+ // - DASHBOARD_TAB_ID (state.js — per-tab id header)
16
+ //
17
+ // Declared with `var` at the top of the global panel <script> so they become
18
+ // window properties; slim's IIFE (dashboard/slim/js/members.js) sets
19
+ // `window.agentData` before invoking `window.MinionsAgents.openAgentDetail(id)`.
20
+
21
+ // Classic panel module state (mirrors dashboard/js/state.js declarations).
22
+ var agentData = [];
23
+ var currentAgentId = null;
24
+ var currentTab = 'thought-process';
25
+
26
+ // Per-tab id echoed to /api/* via safeFetch, mirroring state.js. Best-effort —
27
+ // sessionStorage may throw in locked-down contexts, so fall back to an ephemeral id.
28
+ var DASHBOARD_TAB_ID = (function() {
29
+ try {
30
+ var existing = sessionStorage.getItem('minions-dashboard-tab-id');
31
+ if (existing) return existing;
32
+ var id = 'tab-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 10);
33
+ sessionStorage.setItem('minions-dashboard-tab-id', id);
34
+ return id;
35
+ } catch {
36
+ return 'tab-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 10);
37
+ }
38
+ })();
39
+
40
+ // Abort-on-timeout fetch with the dashboard-tab headers — copied verbatim from
41
+ // dashboard/js/state.js so the reused panel modules (openAgentDetail, charter
42
+ // save, live-stream refresh/steer) behave identically to the classic surface.
43
+ function safeFetch(url, opts) {
44
+ var timeout = (opts && opts.timeout) || 15000;
45
+ var controller = new AbortController();
46
+ var timer = setTimeout(function() { controller.abort(); }, timeout);
47
+ var fetchOpts = Object.assign({}, opts, { signal: controller.signal });
48
+ if (typeof url === 'string' && url.indexOf('/api/') === 0) {
49
+ var headers = Object.assign({}, fetchOpts.headers || {});
50
+ headers['X-Minions-Dashboard-Tab'] = DASHBOARD_TAB_ID;
51
+ headers['X-Minions-Dashboard-Url'] = location.pathname + location.search + location.hash;
52
+ headers['X-Minions-Dashboard-Visibility'] = document.visibilityState || '';
53
+ fetchOpts.headers = headers;
54
+ }
55
+ delete fetchOpts.timeout;
56
+ return fetch(url, fetchOpts).finally(function() { clearTimeout(timer); });
57
+ }
58
+
59
+ // Minimal toast used by the reused charter-editor (save) and live-stream
60
+ // (steering) modules. Mirrors the graceful-degradation contract of the classic
61
+ // showToast (dashboard/js/command-parser.js): warn + return when the target
62
+ // element is absent so a missing #cmd-toast can never throw.
63
+ function showToast(id, msg, ok, durationMs) {
64
+ var el = document.getElementById(id);
65
+ if (!el) { try { console.warn('[showToast] no element with id', id, '— message:', msg); } catch {} return; }
66
+ el.classList.add('cmd-toast');
67
+ el.classList.remove('success', 'error');
68
+ el.classList.add(ok ? 'success' : 'error');
69
+ el.textContent = String(msg == null ? '' : msg);
70
+ el.classList.add('open');
71
+ setTimeout(function() { el.classList.remove('success', 'error', 'open'); }, durationMs || 4000);
72
+ }
73
+
74
+ // ESC closes the slide-in panel (parity with the classic "ESC / Close" button).
75
+ // closeDetail() is defined later in this same global script by detail-panel.js;
76
+ // it is only invoked on keydown (runtime), so load-order is fine.
77
+ document.addEventListener('keydown', function(e) {
78
+ if (e.key !== 'Escape') return;
79
+ var panel = document.getElementById('detail-panel');
80
+ if (panel && panel.classList.contains('open') && typeof closeDetail === 'function') {
81
+ closeDetail();
82
+ }
83
+ });
@@ -703,30 +703,36 @@
703
703
  .agent-detail-row / .tile-item). */
704
704
  #slim-tile-modal .modal { width: 720px; max-width: calc(100vw - 32px); }
705
705
  /* W-mqrdggys000f94a4 / W-mr28ko750010199f / W-mr3l7y0m0007102a /
706
- W-mrtdnknm000med95 the Engine + Queued-work + Plans + PRs tiles embed a
707
- full classic screen (/engine, /work, /plans, /prs) in an iframe; widen the
708
- modal + remove body padding so the embedded screen gets the full
709
- width/height. */
706
+ W-mrtdnknm000med95 / W-mrkpke07000i8ed5 / W-mrz17l6j00098079
707
+ CLASSIC-SCREEN-EMBEDDING MODALS. The Engine + Queued-work + Plans + PRs
708
+ tiles embed a full classic screen (/engine, /work, /plans, /prs) in an
709
+ iframe, and the Settings modal embeds /settings?embed=1. Give them a
710
+ near-fullscreen width (viewport-relative, capped so ultra-wide displays
711
+ keep a small margin) + drop the body padding so the embedded classic
712
+ screen gets the full width/height.
713
+ NOTE (W-mrz17l6j00098079): this is ONE grouped rule on purpose — any
714
+ FUTURE modal that hosts a classic-screen iframe (e.g. the in-flight
715
+ "Automation" composite tile W-mrz0x2a4) only needs its
716
+ `#slim-tile-modal.tile-modal--<key> .modal` / `.modal-body` selectors
717
+ added to the two groups below to inherit the near-fullscreen sizing —
718
+ no new width/height values to pick. Do NOT balloon the base
719
+ #slim-tile-modal 720px rule (list-style watches/dispatches tiles). */
710
720
  #slim-tile-modal.tile-modal--engine .modal,
711
721
  #slim-tile-modal.tile-modal--work .modal,
712
722
  #slim-tile-modal.tile-modal--plans .modal,
713
- #slim-tile-modal.tile-modal--prs .modal { width: 1180px; }
723
+ #slim-tile-modal.tile-modal--prs .modal,
724
+ #slim-settings-modal .modal { width: calc(100vw - 32px); max-width: 1600px; }
714
725
  #slim-tile-modal.tile-modal--engine .modal-body,
715
726
  #slim-tile-modal.tile-modal--work .modal-body,
716
727
  #slim-tile-modal.tile-modal--plans .modal-body,
717
- #slim-tile-modal.tile-modal--prs .modal-body { padding: 0; }
718
- /* W-mrkpke07000i8ed5 — the Settings modal embeds the full classic Settings
719
- screen (/settings?embed=1) in an iframe; widen the modal + drop the body
720
- padding so the embedded settings surface (left rail + content) gets the
721
- full width/height. Mirrors the Queued-work/Plans/PRs tile treatment. */
722
- #slim-settings-modal .modal { width: 1180px; max-width: calc(100vw - 32px); }
728
+ #slim-tile-modal.tile-modal--prs .modal-body,
723
729
  #slim-settings-modal .modal-body { padding: 0; }
724
730
  .slim-engine-embed,
725
731
  .slim-work-embed,
726
732
  .slim-plans-embed,
727
733
  .slim-prs-embed,
728
734
  .slim-settings-embed {
729
- display: block; width: 100%; height: calc(100vh - 160px); min-height: 360px;
735
+ display: block; width: 100%; height: calc(100vh - 96px); min-height: 420px;
730
736
  border: 0; background: var(--bg);
731
737
  }
732
738
  .tile-item {
@@ -1750,4 +1756,134 @@
1750
1756
  font-family: inherit;
1751
1757
  resize: vertical;
1752
1758
  }
1753
- .modal-textarea:focus { outline: none; border-color: var(--blue); }
1759
+ .modal-textarea:focus { outline: none; border-color: var(--blue); }
1760
+ /* ===================================================================
1761
+ Reused CLASSIC agent detail slide-in panel (W-mrz11djx00050594)
1762
+ -------------------------------------------------------------------
1763
+ The panel markup + JS are reused verbatim from the classic dashboard
1764
+ (dashboard/layout.html + dashboard/js/{render-agents,detail-panel,
1765
+ live-stream,charter-editor,render-utils,utils}.js). Slim and classic
1766
+ ship SEPARATE top-level stylesheets with no shared-CSS partial, so the
1767
+ panel's presentation rules are mirrored here. The rule bodies are the
1768
+ classic rules unchanged; they reference classic design tokens
1769
+ (--space-N, --radius-*, --yellow, --purple, --transition-*) that slim
1770
+ either lacks or scales differently. Those tokens are scoped to the
1771
+ panel roots below so every panel descendant resolves them to the
1772
+ classic values WITHOUT disturbing slim's global palette.
1773
+ =================================================================== */
1774
+ #detail-panel, #detail-overlay {
1775
+ --yellow: #d29922; --purple: #bc8cff; --orange: #e3b341;
1776
+ --radius-sm: 4px; --radius-md: 6px; --radius-lg: 8px; --radius-xl: 10px; --radius-full: 50%;
1777
+ --transition-fast: 0.15s; --transition-base: 0.2s; --transition-slow: 0.3s;
1778
+ --space-1: 2px; --space-2: 4px; --space-3: 6px; --space-4: 8px; --space-5: 10px;
1779
+ --space-6: 12px; --space-7: 16px; --space-8: 20px; --space-9: 24px;
1780
+ }
1781
+
1782
+ @keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.3} }
1783
+ .pulse { width: 8px; height: 8px; border-radius: var(--radius-full); background: var(--green); display: inline-block; margin-right: var(--space-3); animation: pulse 2s infinite; }
1784
+
1785
+ .detail-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.6); z-index: 300; }
1786
+ .detail-overlay.open { display: block; }
1787
+ .detail-panel {
1788
+ position: fixed; top: 0; right: 0; width: 55%; max-width: 750px; height: 100vh;
1789
+ background: var(--surface); border-left: 1px solid var(--border);
1790
+ z-index: 301; display: flex; flex-direction: column;
1791
+ transform: translateX(100%); transition: transform 0.25s ease;
1792
+ }
1793
+ .detail-panel.open { transform: translateX(0); }
1794
+ .detail-header {
1795
+ padding: var(--space-7) var(--space-8); border-bottom: 1px solid var(--border);
1796
+ display: flex; justify-content: space-between; align-items: center; flex-shrink: 0;
1797
+ }
1798
+ .detail-header h3 { font-size: var(--text-2xl); color: var(--blue); display: flex; align-items: center; gap: var(--space-4); }
1799
+ .detail-close { background: none; border: 1px solid var(--border); color: var(--muted); font-size: var(--text-md); cursor: pointer; padding: var(--space-2) var(--space-6); border-radius: var(--radius-sm); }
1800
+ .detail-close:hover { color: var(--text); border-color: var(--text); }
1801
+ .detail-body { flex: 1; overflow-y: auto; padding: 0; }
1802
+ .detail-tabs { display: flex; border-bottom: 1px solid var(--border); flex-shrink: 0; background: var(--bg); }
1803
+ .detail-tab {
1804
+ padding: var(--space-5) var(--space-7); font-size: var(--text-md); font-weight: 500; color: var(--muted);
1805
+ cursor: pointer; border-bottom: 2px solid transparent; transition: all var(--transition-base);
1806
+ }
1807
+ .detail-tab:hover { color: var(--text); }
1808
+ .detail-tab.active { color: var(--blue); border-bottom-color: var(--blue); }
1809
+ .detail-content {
1810
+ padding: var(--space-7) var(--space-8); font-size: var(--text-md); line-height: 1.7;
1811
+ white-space: pre-wrap; word-wrap: break-word; font-family: Consolas, monospace; color: var(--muted);
1812
+ }
1813
+ .detail-content h4 { color: var(--text); font-size: var(--text-lg); margin: var(--space-6) 0 var(--space-3) 0; font-family: 'Segoe UI', sans-serif; }
1814
+ .detail-content .section { margin-bottom: var(--space-7); padding: var(--space-6); background: var(--surface2); border: 1px solid var(--border); border-radius: var(--radius-md); }
1815
+ .status-line { display: flex; align-items: center; gap: var(--space-5); padding: var(--space-5) var(--space-7); background: var(--bg); border-bottom: 1px solid var(--border); font-size: var(--text-md); }
1816
+
1817
+ .status-badge { font-size: var(--text-sm); font-weight: 600; padding: var(--space-1) var(--space-4); border-radius: var(--radius-xl); text-transform: uppercase; letter-spacing: 0.5px; }
1818
+ .status-badge.idle { background: var(--surface); color: var(--muted); border: 1px solid var(--border); }
1819
+ .status-badge.working { background: rgba(210,153,34,0.15); color: var(--yellow); border: 1px solid var(--yellow); animation: pulse 1.5s infinite; }
1820
+ .status-badge.done { background: rgba(63,185,80,0.15); color: var(--green); border: 1px solid var(--green); }
1821
+ .status-badge.error { background: rgba(248,81,73,0.15); color: var(--red); border: 1px solid var(--red); }
1822
+
1823
+ .pr-table { width: 100%; border-collapse: collapse; font-size: var(--text-md); table-layout: auto; }
1824
+ .pr-table th, .pr-table td { padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--border); text-align: left; }
1825
+ .pr-table th { color: var(--muted); font-weight: 600; font-size: var(--text-sm); text-transform: uppercase; letter-spacing: 0.4px; }
1826
+
1827
+ .dispatch-type { font-size: var(--text-sm); font-weight: 600; padding: var(--space-1) var(--space-4); border-radius: var(--radius-lg); text-transform: uppercase; white-space: nowrap; }
1828
+ .dispatch-type.implement { background: rgba(88,166,255,0.15); color: var(--blue); }
1829
+ .dispatch-type.review { background: rgba(188,140,255,0.15); color: var(--purple); }
1830
+ .dispatch-type.fix { background: rgba(210,153,34,0.15); color: var(--yellow); }
1831
+ .dispatch-type.analyze { background: rgba(63,185,80,0.15); color: var(--green); }
1832
+ .dispatch-type.explore { background: rgba(139,148,158,0.15); color: var(--muted); }
1833
+ .dispatch-type.test { background: rgba(227,179,65,0.15); color: var(--orange); }
1834
+ .dispatch-type.plan { background: rgba(168,85,247,0.15); color: #a855f7; }
1835
+ .dispatch-type.ask { background: rgba(63,185,80,0.15); color: var(--green); }
1836
+ .dispatch-type.verify { background: rgba(63,185,80,0.2); color: var(--green); }
1837
+ .dispatch-type.meeting { background: rgba(88,166,255,0.2); color: var(--blue); }
1838
+ .dispatch-type.decompose { background: rgba(188,140,255,0.2); color: var(--purple); }
1839
+ .dispatch-type.docs { background: rgba(56,189,248,0.15); color: #38bdf8; }
1840
+ .dispatch-type.setup { background: rgba(20,184,166,0.15); color: #14b8a6; }
1841
+
1842
+ .pr-pager-btn {
1843
+ background: var(--surface2); border: 1px solid var(--border); color: var(--muted);
1844
+ font-size: var(--text-base); padding: var(--space-2) var(--space-5); border-radius: var(--radius-sm); cursor: pointer; transition: all var(--transition-base);
1845
+ }
1846
+ .pr-pager-btn:hover { color: var(--text); border-color: var(--text); }
1847
+
1848
+ .btn-primary-lg {
1849
+ background: var(--blue); color: #fff; border: none; border-radius: var(--radius-sm);
1850
+ padding: 6px 16px; font-size: var(--text-md); cursor: pointer; transition: opacity var(--transition-base);
1851
+ }
1852
+ .btn-primary-lg:hover { opacity: 0.9; }
1853
+
1854
+ .modal-copy { background: var(--surface2); border: 1px solid var(--border); color: var(--muted); font-size: var(--text-base); cursor: pointer; padding: var(--space-2) var(--space-5); border-radius: var(--radius-sm); display: flex; align-items: center; gap: var(--space-2); transition: all var(--transition-base); }
1855
+ .modal-copy:hover { color: var(--text); border-color: var(--text); }
1856
+ .modal-copy.is-success { color: var(--green); border-color: var(--green); }
1857
+ .modal-copy.is-danger { color: var(--red); border-color: var(--red); }
1858
+
1859
+ .artifact-chip {
1860
+ display: inline-flex; align-items: center; gap: var(--space-1);
1861
+ padding: var(--space-1) var(--space-4); border-radius: 10px;
1862
+ font-size: var(--text-sm); cursor: pointer;
1863
+ background: var(--surface2); border: 1px solid var(--border); color: var(--text);
1864
+ transition: border-color var(--transition-base), color var(--transition-base);
1865
+ user-select: none;
1866
+ }
1867
+ .artifact-chip:hover { border-color: var(--blue); color: var(--blue); }
1868
+ .artifact-chip-icon { font-size: var(--text-sm); line-height: 1; }
1869
+ .artifact-chip-label { font-size: var(--text-sm); }
1870
+ .artifact-chip.deleted { cursor: not-allowed; text-decoration: line-through; color: var(--muted); opacity: 0.6; }
1871
+ .artifact-chip-unknown { color: var(--muted); }
1872
+
1873
+ /* Markdown content — consistent styling everywhere renderMd() is used inside the panel */
1874
+ .detail-panel .md-content { font-family: 'Segoe UI', system-ui, sans-serif; font-size: var(--text-md); line-height: 1.6; white-space: normal; word-break: break-word; overflow-x: auto; }
1875
+ .detail-panel .md-content pre { font-family: Consolas, 'Courier New', monospace; white-space: pre-wrap; max-width: 100%; }
1876
+ .detail-panel .md-content code { font-family: Consolas, 'Courier New', monospace; }
1877
+ .detail-panel .md-content table { border-collapse: collapse; margin: 6px 0; width: max-content; min-width: 100%; }
1878
+ .detail-panel .md-content th, .detail-panel .md-content td { padding: 4px 8px; border: 1px solid var(--border); text-align: left; font-size: var(--text-base); white-space: nowrap; }
1879
+ .detail-panel .md-content td { white-space: normal; min-width: 60px; }
1880
+ .detail-panel .md-content th { background: var(--surface); font-weight: 600; }
1881
+ .detail-panel .md-content .md-p { margin: 0 0 6px; }
1882
+ .detail-panel .md-content .md-p:last-child { margin-bottom: 0; }
1883
+ .detail-panel .md-content li { margin: 1px 0; }
1884
+
1885
+ /* Floating toast target for the reused panel (charter save / steering feedback). */
1886
+ .cmd-toast { position: fixed; bottom: 16px; right: 16px; z-index: 500; max-width: 360px; padding: 10px 14px; border-radius: 6px; background: var(--surface2); border: 1px solid var(--border); color: var(--text); font-size: var(--text-md); box-shadow: 0 4px 16px rgba(0,0,0,0.4); opacity: 0; pointer-events: none; transition: opacity 0.2s ease; }
1887
+ .cmd-toast.open { opacity: 1; }
1888
+ .cmd-toast.success { border-color: var(--green); }
1889
+ .cmd-toast.error { border-color: var(--red); }
@@ -79,6 +79,18 @@ const SLIM_JS_ORDER = [
79
79
  'command-send', 'status', 'members', 'modals-tiles', 'history', 'pinned', 'knowledge', 'plans',
80
80
  ];
81
81
 
82
+ // W-mrz11djx00050594 — the CLASSIC agent detail slide-in panel, reused verbatim
83
+ // inside Slim-UX. These are the real dashboard/js/*.js modules the classic
84
+ // dashboard ships (no fork); they are concatenated after slim/panel-bootstrap.js
85
+ // into a single GLOBAL, non-strict <script> (the /* __PANEL_JS__ */ slot) so
86
+ // they run in the same global scope they were written for, separate from slim's
87
+ // strict IIFE. Order is load-bearing: helpers before consumers, and
88
+ // charter-editor MUST precede detail-panel (see the "charter-editor registered
89
+ // before detail-panel" test).
90
+ const SLIM_CLASSIC_PANEL_JS = [
91
+ 'utils', 'render-utils', 'charter-editor', 'live-stream', 'render-agents', 'detail-panel',
92
+ ];
93
+
82
94
  // Cache for the assembled slim source fragments (layout/css/body/js). Keyed on
83
95
  // the input file mtimes so dev edits still take effect on the next request
84
96
  // without a server restart, while production serves repeated requests without
@@ -92,8 +104,10 @@ function _slimSourcePaths(slimDir) {
92
104
  path.join(slimDir, 'layout.html'),
93
105
  path.join(slimDir, 'styles.css'),
94
106
  path.join(slimDir, 'body.html'),
107
+ path.join(slimDir, 'panel-bootstrap.js'),
95
108
  ...DASHBOARD_SHARED_JS.map(f => path.join(MINIONS_DIR, 'dashboard', 'shared', f + '.js')),
96
109
  ...SLIM_JS_ORDER.map(f => path.join(slimDir, 'js', f + '.js')),
110
+ ...SLIM_CLASSIC_PANEL_JS.map(f => path.join(MINIONS_DIR, 'dashboard', 'js', f + '.js')),
97
111
  ];
98
112
  }
99
113
 
@@ -112,7 +126,14 @@ function buildSlimHtml(opts) {
112
126
  if (!_slimPartsCache
113
127
  || _slimPartsCache.mtimes.length !== mtimes.length
114
128
  || _slimPartsCache.mtimes.some((m, i) => m !== mtimes[i])) {
115
- const [lp, cp, bp, ...jsPaths] = paths;
129
+ // paths layout: [layout, styles, body, panel-bootstrap,
130
+ // ...DASHBOARD_SHARED_JS, ...SLIM_JS_ORDER, ...SLIM_CLASSIC_PANEL_JS].
131
+ // The slim IIFE gets shared + slim js; the classic panel gets its own
132
+ // global <script> (bootstrap + classic modules) — see /* __PANEL_JS__ */.
133
+ const [lp, cp, bp, bootstrapPath, ...restPaths] = paths;
134
+ const iifeCount = DASHBOARD_SHARED_JS.length + SLIM_JS_ORDER.length;
135
+ const iifePaths = restPaths.slice(0, iifeCount);
136
+ const panelPaths = restPaths.slice(iifeCount);
116
137
  _slimPartsCache = {
117
138
  mtimes,
118
139
  layout: safeRead(lp),
@@ -121,10 +142,14 @@ function buildSlimHtml(opts) {
121
142
  // Join with a newline so a fragment that loses its trailing newline
122
143
  // can't glue two statements together (e.g. `}var foo`). Parts share one
123
144
  // IIFE scope, so order is load-bearing — see SLIM_JS_ORDER.
124
- js: jsPaths.map(p => safeRead(p)).join('\n'),
145
+ js: iifePaths.map(p => safeRead(p)).join('\n'),
146
+ // Reused classic agent-detail panel — bootstrap glue first, then the real
147
+ // classic modules. Emitted into a separate GLOBAL, non-strict <script>
148
+ // (the /* __PANEL_JS__ */ slot) so they run in their original scope.
149
+ panelJs: [bootstrapPath, ...panelPaths].map(p => safeRead(p)).join('\n'),
125
150
  };
126
151
  }
127
- const { layout, css, body, js } = _slimPartsCache;
152
+ const { layout, css, body, js, panelJs } = _slimPartsCache;
128
153
 
129
154
  // Feature-flag bootstrap (window.MINIONS_FEATURES) injected at the top of the
130
155
  // slim IIFE. serveSlimUx passes the live flags as JSON via opts.featuresJson;
@@ -138,6 +163,7 @@ function buildSlimHtml(opts) {
138
163
  // an exact '\n' match would silently no-op and ship an empty body on CRLF).
139
164
  .replace(/<!-- __BODY__ -->\r?\n/, () => body)
140
165
  .replace('{{minions_features}}', () => featuresJson)
166
+ .replace('/* __PANEL_JS__ */', () => panelJs)
141
167
  .replace('/* __JS__ */', () => js);
142
168
  }
143
169
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2411",
3
+ "version": "0.1.2413",
4
4
  "description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
5
5
  "bin": {
6
6
  "minions": "bin/minions.js"