@yemi33/minions 0.1.2388 → 0.1.2390

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.
@@ -627,7 +627,7 @@ function _renderPlanModal(normalizedFile, raw, lastMod) {
627
627
  text = 'Project: ' + (plan.project || '?') +
628
628
  '\nStrategy: ' + (plan.branch_strategy || 'parallel') +
629
629
  '\nBranch: ' + (plan.feature_branch || 'per-item') +
630
- '\nStatus: ' + (plan.status || 'active') +
630
+ '\nStatus: ' + (plan.status || (plan.requires_approval && !plan.approvedAt ? 'awaiting-approval' : 'active')) +
631
631
  '\nGenerated by: ' + (plan.generated_by || '?') + ' on ' + (plan.generated_at || '?') +
632
632
  '\n\n--- Items (' + (plan.missing_features || []).length + ') ---\n\n' + items +
633
633
  (plan.open_questions?.length ? '\n\n--- Open Questions ---\n\n' + plan.open_questions.map(q => '\u2022 ' + q).join('\n') : '');
@@ -1445,3 +1445,33 @@ async function removeProject(name) {
1445
1445
  }
1446
1446
 
1447
1447
  window.MinionsSettings = { openSettings, saveSettings, addProject, removeProject, resetSettingsToDefaults, _selectSettingsTab, _applySettingsSearch };
1448
+
1449
+ // ── Settings embed mode (Slim UX parity — W-mrkpke07000i8ed5) ───────────
1450
+ // The Slim UX Settings modal embeds this LITERAL classic Settings surface in
1451
+ // an iframe at /settings?embed=1 (chrome-off) so there is ONE settings UI, not
1452
+ // two (CLAUDE.md "Slim UX vs Classic UX — reuse, don't fork"; mirrors the
1453
+ // Queued-work/Plans/PRs cockpit tiles). Slim and classic are separate IIFE
1454
+ // bundles with no shared runtime scope, so slim can't call openSettings()
1455
+ // in-process — it iframes the real classic screen instead. On boot, when we
1456
+ // ARE the embedded /settings page, auto-open the modal and tag <html> so the
1457
+ // embed CSS renders the settings modal full-bleed inside the iframe. Only the
1458
+ // /settings path triggers this; every other embedded page (/work, /plans,
1459
+ // /prs) is untouched.
1460
+ (function bootSettingsEmbed() {
1461
+ if (typeof window === 'undefined' || typeof document === 'undefined') return;
1462
+ try {
1463
+ var isSettingsPath = window.location.pathname.replace(/\/+$/, '') === '/settings';
1464
+ var isEmbed = /[?&]embed=1(?:&|$)/.test(window.location.search);
1465
+ if (!isSettingsPath || !isEmbed) return;
1466
+ } catch (e) { return; }
1467
+ try { document.documentElement.classList.add('embed-settings'); } catch (e) { /* no DOM attr */ }
1468
+ function openWhenReady() {
1469
+ if (typeof openSettings === 'function') { openSettings(); return; }
1470
+ setTimeout(openWhenReady, 30); // wait for the settings module + deps to register
1471
+ }
1472
+ if (document.readyState === 'loading') {
1473
+ document.addEventListener('DOMContentLoaded', function() { setTimeout(openWhenReady, 0); });
1474
+ } else {
1475
+ setTimeout(openWhenReady, 0);
1476
+ }
1477
+ })();
@@ -270,16 +270,19 @@
270
270
  </div>
271
271
  </div>
272
272
 
273
- <!-- Settings dialog (preserved from r2): lists experimental flags so the
274
- user can flip 'slim-ux' off and revert to the original dashboard. -->
273
+ <!-- Settings dialog embeds the LITERAL classic dashboard Settings surface
274
+ in an iframe (/settings?embed=1) so slim shows the FULL settings (engine
275
+ toggles, runtime/model, projects, agents, routing, feature flags incl.
276
+ slim-ux) with zero forked rendering. See dashboard/slim/js/settings.js
277
+ (W-mrkpke07000i8ed5 / "reuse, don't fork"). -->
275
278
  <div class="modal-bg" id="slim-settings-modal">
276
279
  <div class="modal">
277
280
  <div class="modal-header">
278
- <h3>Settings &middot; Experimental flags</h3>
281
+ <h3>Settings</h3>
279
282
  <button id="slim-settings-close" class="icon-btn" title="Close">&times;</button>
280
283
  </div>
281
284
  <div class="modal-body" id="slim-settings-body">
282
- <p>Loading flags&hellip;</p>
285
+ <p>Loading settings&hellip;</p>
283
286
  </div>
284
287
  </div>
285
288
  </div>
@@ -1,80 +1,39 @@
1
1
  // ── Settings dialog ────────────────────────────────────────────
2
- async function openSlimSettings() {
2
+ // Reuses the LITERAL classic dashboard Settings surface instead of a
3
+ // slim-only feature-flag list, so there is ONE settings UI, not two
4
+ // (W-mrkpke07000i8ed5 / CLAUDE.md "Slim UX vs Classic UX — reuse, don't
5
+ // fork"; mirrors the Queued-work/Plans/PRs cockpit tiles). Slim and classic
6
+ // are two separate IIFE bundles with no shared runtime scope, so we can't
7
+ // call the classic openSettings() in-process; instead we embed the real
8
+ // classic /settings screen in an iframe with the chrome-off ?embed=1 mode.
9
+ // The iframed page IS the classic Settings modal — every engine toggle,
10
+ // runtime/model, projects, agents, routing, feature flags (incl. slim-ux) —
11
+ // backed by the same /api/settings + /api/features endpoints, with zero
12
+ // duplicated rendering logic. Classic is reachable at /settings even with
13
+ // slim-ux ON (only / is taken over). Built with createElement (no innerHTML)
14
+ // to satisfy the dashboard no-unsanitized lint gate.
15
+ function openSlimSettings() {
3
16
  var modal = document.getElementById('slim-settings-modal');
4
17
  var body = document.getElementById('slim-settings-body');
5
- body.innerHTML = '<p>Loading flags&hellip;</p>';
18
+ if (!modal || !body) return;
19
+ body.textContent = '';
20
+ var frame = document.createElement('iframe');
21
+ frame.className = 'slim-settings-embed';
22
+ frame.src = '/settings?embed=1';
23
+ frame.title = 'Settings';
24
+ frame.addEventListener('load', function() {
25
+ var frameDocument = frame.contentDocument;
26
+ if (!frameDocument) return;
27
+ // Capture before the classic modal handler so only the parent modal closes.
28
+ frameDocument.addEventListener('keydown', function(ev) {
29
+ if (ev.key !== 'Escape' && ev.keyCode !== 27) return;
30
+ ev.preventDefault();
31
+ ev.stopImmediatePropagation();
32
+ closeSlimSettings();
33
+ }, true);
34
+ });
35
+ body.appendChild(frame);
6
36
  modal.classList.add('open');
7
- try {
8
- var res = await fetch('/api/features');
9
- if (!res.ok) throw new Error('HTTP ' + res.status);
10
- var data = await res.json();
11
- var flags = (data && data.features) || [];
12
-
13
- // Build the body as a document fragment so we avoid .innerHTML for
14
- // the dynamic parts (SEC-03 ratchet — see DYNAMIC_INNERHTML_BASELINE
15
- // in test/unit.test.js). Static intro paragraph uses innerHTML
16
- // because it carries a <code> tag and is a pure string literal
17
- // (exempted by _isStaticInnerHtmlRhs).
18
- var intro = document.createElement('p');
19
- intro.innerHTML = 'Toggle <code>slim-ux</code> off to return to the original dashboard. Advanced settings (agents, projects, runtime) live there for now.';
20
- var frag = document.createDocumentFragment();
21
- frag.appendChild(intro);
22
-
23
- if (flags.length === 0) {
24
- var none = document.createElement('p');
25
- none.innerHTML = 'No experimental flags registered. Add entries to <code>engine/features.js</code>.';
26
- frag.appendChild(none);
27
- } else {
28
- for (var i = 0; i < flags.length; i++) {
29
- var f = flags[i];
30
- var row = document.createElement('div');
31
- row.className = 'flag-row';
32
- var info = document.createElement('div');
33
- var name = document.createElement('div');
34
- name.className = 'flag-name';
35
- name.textContent = f.id;
36
- var desc = document.createElement('div');
37
- desc.className = 'flag-desc';
38
- desc.textContent = f.description || '';
39
- info.appendChild(name);
40
- info.appendChild(desc);
41
- var lbl = document.createElement('label');
42
- lbl.className = 'flag-toggle';
43
- var input = document.createElement('input');
44
- input.type = 'checkbox';
45
- input.setAttribute('data-flag', f.id);
46
- if (f.enabled) input.checked = true;
47
- var stateSpan = document.createElement('span');
48
- stateSpan.textContent = f.enabled ? 'On' : 'Off';
49
- lbl.appendChild(input);
50
- lbl.appendChild(stateSpan);
51
- row.appendChild(info);
52
- row.appendChild(lbl);
53
- frag.appendChild(row);
54
- }
55
- }
56
-
57
- // TEMP: deep-link to the full dashboard's settings page. The slim
58
- // intentionally doesn't reimplement that whole UI — Carlos asked us
59
- // to keep settings sprawl off the main slim view.
60
- var link = document.createElement('a');
61
- link.className = 'settings-link';
62
- link.href = '/?fullDashboard=1';
63
- link.title = 'Open the full dashboard’s settings page';
64
- link.textContent = 'Advanced settings → full dashboard';
65
- frag.appendChild(link);
66
-
67
- body.replaceChildren(frag);
68
- var inputs = body.querySelectorAll('input[type=checkbox][data-flag]');
69
- for (var j = 0; j < inputs.length; j++) {
70
- inputs[j].addEventListener('change', onFlagToggle);
71
- }
72
- } catch (e) {
73
- var err = document.createElement('p');
74
- err.style.color = 'var(--red)';
75
- err.textContent = 'Failed to load flags: ' + (e && e.message ? e.message : 'unknown error');
76
- body.replaceChildren(err);
77
- }
78
37
  }
79
38
 
80
39
  function closeSlimSettings() {
@@ -99,7 +58,7 @@
99
58
  })();
100
59
 
101
60
  // POST a feature-flag toggle; throws on a non-2xx so callers can branch on
102
- // failure. Shared by onFlagToggle() and the one-click returnToClassicDashboard().
61
+ // failure. Used by the one-click returnToClassicDashboard().
103
62
  async function postFeatureToggle(id, enabled) {
104
63
  var res = await fetch('/api/features/toggle', {
105
64
  method: 'POST',
@@ -223,24 +182,6 @@
223
182
  bindModalClose('slim-bug-modal', 'slim-bug-close');
224
183
  })();
225
184
 
226
- async function onFlagToggle(ev) {
227
- var input = ev.currentTarget;
228
- var id = input.getAttribute('data-flag');
229
- var enabled = input.checked;
230
- var label = input.parentElement.querySelector('span');
231
- if (label) label.textContent = enabled ? 'On' : 'Off';
232
- try {
233
- await postFeatureToggle(id, enabled);
234
- if (id === 'slim-ux' && !enabled) {
235
- location.reload();
236
- }
237
- } catch (e) {
238
- input.checked = !enabled;
239
- if (label) label.textContent = !enabled ? 'On' : 'Off';
240
- alert('Toggle failed: ' + e.message);
241
- }
242
- }
243
-
244
185
  // ── Action prompt modal (TEMP: routes through chat) ──────────────
245
186
  // Each of the 4 action buttons opens this modal with prompt text
246
187
  // tailored to the concept. On submit we drop the user's text into the
@@ -690,9 +690,16 @@
690
690
  #slim-tile-modal.tile-modal--work .modal-body,
691
691
  #slim-tile-modal.tile-modal--plans .modal-body,
692
692
  #slim-tile-modal.tile-modal--prs .modal-body { padding: 0; }
693
+ /* W-mrkpke07000i8ed5 — the Settings modal embeds the full classic Settings
694
+ screen (/settings?embed=1) in an iframe; widen the modal + drop the body
695
+ padding so the embedded settings surface (left rail + content) gets the
696
+ full width/height. Mirrors the Queued-work/Plans/PRs tile treatment. */
697
+ #slim-settings-modal .modal { width: 1180px; max-width: calc(100vw - 32px); }
698
+ #slim-settings-modal .modal-body { padding: 0; }
693
699
  .slim-work-embed,
694
700
  .slim-plans-embed,
695
- .slim-prs-embed {
701
+ .slim-prs-embed,
702
+ .slim-settings-embed {
696
703
  display: block; width: 100%; height: calc(100vh - 160px); min-height: 360px;
697
704
  border: 0; background: var(--bg);
698
705
  }
@@ -1705,32 +1712,6 @@
1705
1712
  }
1706
1713
  .btn-primary:hover, .btn-secondary:hover { filter: brightness(1.1); }
1707
1714
 
1708
- .flag-row {
1709
- display: flex;
1710
- align-items: center;
1711
- justify-content: space-between;
1712
- padding: 10px 0;
1713
- border-bottom: 1px solid var(--border);
1714
- }
1715
- .flag-row:last-child { border-bottom: none; }
1716
- .flag-name { font-size: var(--text-lg); font-weight: 600; }
1717
- .flag-desc { font-size: var(--text-base); color: var(--muted); margin-top: 2px; }
1718
- .flag-toggle { display: flex; align-items: center; gap: 8px; font-size: var(--text-md); }
1719
- .flag-toggle input { cursor: pointer; }
1720
- .settings-link {
1721
- display: block;
1722
- margin-top: 14px;
1723
- padding: 10px 12px;
1724
- background: var(--surface2);
1725
- border: 1px solid var(--border);
1726
- border-radius: var(--radius);
1727
- color: var(--blue);
1728
- text-decoration: none;
1729
- font-size: var(--text-md);
1730
- text-align: center;
1731
- }
1732
- .settings-link:hover { filter: brightness(1.1); }
1733
-
1734
1715
  .modal-textarea {
1735
1716
  width: 100%;
1736
1717
  min-height: 96px;
@@ -1645,3 +1645,18 @@
1645
1645
  base .page-layout flex row already gives .page-content full width plus a
1646
1646
  bounded height to scroll within. */
1647
1647
  html.embed .page-content { padding: 12px 16px; overflow-y: auto; }
1648
+
1649
+ /* W-mrkpke07000i8ed5 — settings-embed mode. The Slim UX Settings modal iframes
1650
+ the classic Settings surface at /settings?embed=1; settings.js#bootSettingsEmbed
1651
+ auto-opens the modal and tags <html class="embed-settings">. Render the
1652
+ settings modal full-bleed inside the iframe (no dim backdrop, no rounded
1653
+ card, fill the frame), hide the empty home page-content behind it, and hide
1654
+ the modal close X — the parent slim modal owns closing. */
1655
+ html.embed-settings .page-content { display: none !important; }
1656
+ html.embed-settings #modal.modal-bg { background: var(--bg); }
1657
+ html.embed-settings #modal .modal,
1658
+ html.embed-settings #modal .modal.modal-wide {
1659
+ width: 100%; max-width: none; height: 100%; max-height: none;
1660
+ border: none; border-radius: 0;
1661
+ }
1662
+ html.embed-settings #modal .modal-close { display: none; }
@@ -1,6 +1,5 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
- const dir = path.resolve(__dirname, '..');
4
3
  const { safeJson, MINIONS_DIR, DONE_STATUSES } = require('./shared');
5
4
 
6
5
  console.log('=== Work Items (non-done) ===');
@@ -12,8 +11,7 @@ items.filter(i => !DONE_STATUSES.has(i.status)).forEach(i => {
12
11
 
13
12
  console.log('\n=== Agent Status (derived from dispatch) ===');
14
13
  const { getAgentStatus } = require('./queries');
15
- let config = {};
16
- try { config = safeJson(path.join(MINIONS_DIR, 'config.json')) || {}; } catch {}
14
+ const config = safeJson(path.join(MINIONS_DIR, 'config.json')) || {};
17
15
  const agents = Object.keys(config.agents || {});
18
16
  if (agents.length === 0) {
19
17
  console.log('(no agents configured)');
@@ -26,6 +24,6 @@ if (agents.length === 0) {
26
24
 
27
25
  console.log('\n=== Inbox ===');
28
26
  try {
29
- const inbox = fs.readdirSync(path.join(dir, 'notes', 'inbox')).filter(f => f.endsWith('.md'));
27
+ const inbox = fs.readdirSync(path.join(MINIONS_DIR, 'notes', 'inbox')).filter(f => f.endsWith('.md'));
30
28
  console.log(inbox.length + ' files:', inbox.join(', '));
31
29
  } catch { console.log('empty'); }
@@ -42,7 +42,7 @@ function _capNotesPreservingOverflow(newContent) {
42
42
  // No section boundary before the cap — fall back to keeping header + the
43
43
  // most recent sections, archiving the middle.
44
44
  const sections = newContent.split('\n---\n\n### ');
45
- if (sections.length > 10) {
45
+ if (sections.length > 9) {
46
46
  kept = sections[0] + '\n---\n\n### ' + sections.slice(-8).join('\n---\n\n### ');
47
47
  const middle = sections.slice(1, -8);
48
48
  if (middle.length) dropped = '\n---\n\n### ' + middle.join('\n---\n\n### ');
package/engine/queries.js CHANGED
@@ -2176,7 +2176,7 @@ function getPrdInfo(config) {
2176
2176
  }
2177
2177
  existingPrds.push({
2178
2178
  file: pf,
2179
- status: plan.status || 'active',
2179
+ status: plan.status || (plan.requires_approval && !plan.approvedAt ? 'awaiting-approval' : 'active'),
2180
2180
  planStale: planStale || plan.planStale || false,
2181
2181
  completedAt: plan.completedAt || '',
2182
2182
  _archived: isArch,
@@ -2195,7 +2195,7 @@ function getPrdInfo(config) {
2195
2195
  ? (prdItemIdByKey.get(`${archived ? 1 : 0} ${pf} ${f.id}`) ?? null)
2196
2196
  : null;
2197
2197
  allPrdItems.push({
2198
- ...f, _source: pf, _planStatus: plan.status || 'active',
2198
+ ...f, _source: pf, _planStatus: plan.status || (plan.requires_approval && !plan.approvedAt ? 'awaiting-approval' : 'active'),
2199
2199
  _planSummary: plan.plan_summary || pf, _planProject: plan.project || '',
2200
2200
  _archived: isArch, _sourcePlan: plan.source_plan || '',
2201
2201
  _branchStrategy: plan.branch_strategy || 'parallel',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2388",
3
+ "version": "0.1.2390",
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"