@yemi33/minions 0.1.2414 → 0.1.2415

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.
@@ -185,7 +185,7 @@ const RENDER_VERSIONS = {
185
185
  // Bumped to 4 by W-mqrdavob (Agents table: per-agent charter editor column).
186
186
  // Bumped to 5 by W-mr0qs0vw (Worker Pool: CC session idle-timeout field).
187
187
  // Bumped to 6 for the fleet ACP pool opt-in default and warning copy.
188
- settings: 6,
188
+ settings: 7,
189
189
  };
190
190
  const _sectionCache = {};
191
191
  const _lastValueByKey = {};
@@ -713,6 +713,13 @@ async function openSettings() {
713
713
  // non-dispatch toggles that historically lived under Auto-fix; placed
714
714
  // adjacent to Auto-fix so operators following the legacy mental model find
715
715
  // the moved flags quickly.
716
+ // W-mryz3r7h — a section may opt into a collapsible rail fold by declaring a
717
+ // `group`. Grouped sections render as an indented child list beneath a fold
718
+ // header (see RAIL_GROUPS + the railHtml builder below); their PANES stay in
719
+ // the content area unchanged. Copilot/Claude Tuning + Budget + the former
720
+ // standalone Advanced pane now live under the "Advanced" fold (collapsed by
721
+ // default). The former standalone Advanced leaf is nested here too so there is
722
+ // only ever one thing labeled "Advanced".
716
723
  const sections = [
717
724
  { id: 'runtime', label: 'Agents Runtime & Models', featured: true, html: paneRuntime },
718
725
  { id: 'autofix', label: 'Auto-fix & Review Loop', featured: true, html: paneAutoFix },
@@ -723,15 +730,19 @@ async function openSettings() {
723
730
  { id: 'polling', label: 'Polling', html: panePolling },
724
731
  { id: 'concurrency', label: 'Concurrency & Timeouts', html: paneConcurrency },
725
732
  { id: 'worktree', label: 'Worker Pool & Worktrees', html: paneWorktree },
726
- { id: 'copilot', label: 'Copilot Tuning', html: paneCopilot },
727
- { id: 'claude', label: 'Claude Tuning', html: paneClaude },
728
733
  { id: 'harness', label: 'Runtime Harness', html: paneHarness },
729
- { id: 'budget', label: 'Budget', html: paneBudget },
730
734
  { id: 'maxturns', label: 'Max Turns', html: paneMaxTurns },
731
735
  { id: 'features', label: 'Feature Flags', html: paneFeatures },
732
- { id: 'advanced', label: 'Advanced', html: paneAdvanced },
736
+ { id: 'copilot', label: 'Copilot Tuning', html: paneCopilot, group: 'advanced' },
737
+ { id: 'claude', label: 'Claude Tuning', html: paneClaude, group: 'advanced' },
738
+ { id: 'budget', label: 'Budget', html: paneBudget, group: 'advanced' },
739
+ { id: 'advanced', label: 'Advanced', html: paneAdvanced, group: 'advanced' },
733
740
  ];
734
741
 
742
+ // Rail fold registry: group id → display label. Sections opt in via `group`.
743
+ const RAIL_GROUPS = { advanced: { label: 'Advanced' } };
744
+ const ADV_FOLD_KEY = 'minions.settings.advancedFoldOpen';
745
+
735
746
  // localStorage-persisted active tab. Falls back to the first section when no
736
747
  // saved value or the saved value points at a section we no longer render.
737
748
  const SAVED_TAB_KEY = 'minions.settings.activeTab';
@@ -741,12 +752,41 @@ async function openSettings() {
741
752
  if (saved && sections.some(s => s.id === saved)) activeTab = saved;
742
753
  } catch { /* localStorage may be blocked in some embeds */ }
743
754
 
744
- const railHtml = sections.map(s =>
745
- '<button class="settings-rail-btn' + (s.featured ? ' featured' : '') + (s.id === activeTab ? ' active' : '') + '" data-settings-tab="' + s.id + '" type="button">' +
755
+ // Compute the Advanced fold's initial expanded state. Default (no saved
756
+ // value) is COLLAPSED. Persisted choice survives reloads. If the restored
757
+ // active tab lives inside the fold, force it open so the active rail button
758
+ // is visible on load.
759
+ let advancedFoldOpen = false;
760
+ try { advancedFoldOpen = localStorage.getItem(ADV_FOLD_KEY) === '1'; } catch { /* localStorage may be blocked in some embeds */ }
761
+ if (sections.some(s => s.id === activeTab && s.group === 'advanced')) advancedFoldOpen = true;
762
+ const foldStates = { advanced: advancedFoldOpen };
763
+
764
+ const railBtnHtml = (s, child) =>
765
+ '<button class="settings-rail-btn' + (s.featured ? ' featured' : '') + (child ? ' settings-rail-btn--child' : '') + (s.id === activeTab ? ' active' : '') + '" data-settings-tab="' + s.id + '" type="button">' +
746
766
  '<span class="rail-label">' + escHtml(s.label) + '</span>' +
747
767
  '<span class="match-count" style="display:none">0</span>' +
748
- '</button>'
749
- ).join('');
768
+ '</button>';
769
+
770
+ // Walk the sections in order. Leaves render directly; the first grouped
771
+ // section emits the whole fold (header + indented child list), and later
772
+ // members of the same group are skipped (they were rendered with the header).
773
+ const renderedGroups = new Set();
774
+ const railHtml = sections.map(function(s) {
775
+ if (!s.group) return railBtnHtml(s, false);
776
+ if (renderedGroups.has(s.group)) return '';
777
+ renderedGroups.add(s.group);
778
+ const members = sections.filter(x => x.group === s.group);
779
+ const open = !!foldStates[s.group];
780
+ const label = (RAIL_GROUPS[s.group] && RAIL_GROUPS[s.group].label) || s.group;
781
+ return '<button class="settings-rail-fold' + (open ? ' open' : '') + '" data-settings-fold="' + s.group + '" type="button" aria-expanded="' + (open ? 'true' : 'false') + '">' +
782
+ '<span class="fold-chevron" aria-hidden="true"></span>' +
783
+ '<span class="rail-label">' + escHtml(label) + '</span>' +
784
+ '<span class="match-count" style="display:none">0</span>' +
785
+ '</button>' +
786
+ '<div class="settings-rail-group' + (open ? ' open' : '') + '" data-settings-group="' + s.group + '" role="group" aria-label="' + escHtml(label) + '">' +
787
+ members.map(m => railBtnHtml(m, true)).join('') +
788
+ '</div>';
789
+ }).join('');
750
790
 
751
791
  const panesHtml = sections.map(s =>
752
792
  '<div class="settings-pane' + (s.id === activeTab ? ' active' : '') + '" data-settings-pane="' + s.id + '">' +
@@ -806,6 +846,20 @@ async function openSettings() {
806
846
  });
807
847
  });
808
848
 
849
+ // ── Wire up fold headers (expand/collapse the Advanced group) ───────────
850
+ // <button> headers are keyboard-operable natively (Enter/Space fire click).
851
+ // The user's choice is persisted so it survives reloads; the render-time
852
+ // default is collapsed unless the active tab lives inside the fold.
853
+ body.querySelectorAll('[data-settings-fold]').forEach(function(btn) {
854
+ btn.addEventListener('click', function() {
855
+ const gid = btn.getAttribute('data-settings-fold');
856
+ const open = _toggleSettingsFold(gid);
857
+ if (gid === 'advanced') {
858
+ try { localStorage.setItem(ADV_FOLD_KEY, open ? '1' : '0'); } catch { /* ignore */ }
859
+ }
860
+ });
861
+ });
862
+
809
863
  // ── Cross-tab search box ───────────────────────────────────────────────
810
864
  // Filters every [data-search] row across every pane. Empty query restores
811
865
  // the active-tab view; non-empty query shows ALL panes with matches and
@@ -1257,6 +1311,32 @@ function _selectSettingsTab(id) {
1257
1311
  body.querySelectorAll('[data-settings-pane]').forEach(function(pane) {
1258
1312
  pane.classList.toggle('active', pane.getAttribute('data-settings-pane') === id);
1259
1313
  });
1314
+ // If the selected tab lives inside a collapsed fold, expand the fold so its
1315
+ // rail button is visible. The pane renders regardless of fold state.
1316
+ const activeBtn = body.querySelector('[data-settings-tab="' + id + '"]');
1317
+ const group = activeBtn && activeBtn.closest('[data-settings-group]');
1318
+ if (group) {
1319
+ group.classList.add('open');
1320
+ const gid = group.getAttribute('data-settings-group');
1321
+ const fold = body.querySelector('[data-settings-fold="' + gid + '"]');
1322
+ if (fold) { fold.classList.add('open'); fold.setAttribute('aria-expanded', 'true'); }
1323
+ }
1324
+ }
1325
+
1326
+ // Expand/collapse a rail fold. Returns the resulting open state (or undefined
1327
+ // if the fold isn't mounted). `forceState` (boolean) sets an explicit state;
1328
+ // omitting it toggles. Keeps the header's aria-expanded + chevron in sync.
1329
+ function _toggleSettingsFold(groupId, forceState) {
1330
+ const body = document.getElementById('modal-body');
1331
+ if (!body) return undefined;
1332
+ const fold = body.querySelector('[data-settings-fold="' + groupId + '"]');
1333
+ const group = body.querySelector('[data-settings-group="' + groupId + '"]');
1334
+ if (!fold || !group) return undefined;
1335
+ const open = typeof forceState === 'boolean' ? forceState : !group.classList.contains('open');
1336
+ group.classList.toggle('open', open);
1337
+ fold.classList.toggle('open', open);
1338
+ fold.setAttribute('aria-expanded', open ? 'true' : 'false');
1339
+ return open;
1260
1340
  }
1261
1341
 
1262
1342
  function _applySettingsSearch(rawQuery) {
@@ -1281,6 +1361,25 @@ function _applySettingsSearch(rawQuery) {
1281
1361
  const badge = btn.querySelector('.match-count');
1282
1362
  if (badge) badge.style.display = 'none';
1283
1363
  });
1364
+ // Restore each fold's resting state: persisted collapsed default, but kept
1365
+ // open if an active child tab lives inside it (so the visible pane's rail
1366
+ // button isn't hidden). Clears any search-driven fold badge/no-match.
1367
+ body.querySelectorAll('[data-settings-fold]').forEach(function(fold) {
1368
+ const gid = fold.getAttribute('data-settings-fold');
1369
+ const group = body.querySelector('[data-settings-group="' + gid + '"]');
1370
+ if (!group) return;
1371
+ fold.classList.remove('no-match');
1372
+ const foldBadge = fold.querySelector('.match-count');
1373
+ if (foldBadge) foldBadge.style.display = 'none';
1374
+ let open = false;
1375
+ if (gid === 'advanced') {
1376
+ try { open = localStorage.getItem('minions.settings.advancedFoldOpen') === '1'; } catch { /* ignore */ }
1377
+ }
1378
+ if (group.querySelector('[data-settings-tab].active')) open = true;
1379
+ group.classList.toggle('open', open);
1380
+ fold.classList.toggle('open', open);
1381
+ fold.setAttribute('aria-expanded', open ? 'true' : 'false');
1382
+ });
1284
1383
  return;
1285
1384
  }
1286
1385
 
@@ -1311,6 +1410,32 @@ function _applySettingsSearch(rawQuery) {
1311
1410
  }
1312
1411
  }
1313
1412
  });
1413
+
1414
+ // Fold handling during search: expand any fold that has a matching child so
1415
+ // results aren't hidden behind a collapsed header, aggregate the child match
1416
+ // counts onto the fold header badge, and dim folds with zero matches. Runs
1417
+ // AFTER the per-pane loop so child rail badges are already computed.
1418
+ body.querySelectorAll('[data-settings-fold]').forEach(function(fold) {
1419
+ const gid = fold.getAttribute('data-settings-fold');
1420
+ const group = body.querySelector('[data-settings-group="' + gid + '"]');
1421
+ if (!group) return;
1422
+ let total = 0;
1423
+ let anyMatch = false;
1424
+ group.querySelectorAll('[data-settings-tab]').forEach(function(cb) {
1425
+ if (!cb.classList.contains('no-match')) anyMatch = true;
1426
+ const badge = cb.querySelector('.match-count');
1427
+ if (badge && badge.style.display !== 'none') total += Number(badge.textContent) || 0;
1428
+ });
1429
+ group.classList.toggle('open', anyMatch);
1430
+ fold.classList.toggle('open', anyMatch);
1431
+ fold.setAttribute('aria-expanded', anyMatch ? 'true' : 'false');
1432
+ fold.classList.toggle('no-match', !anyMatch);
1433
+ const foldBadge = fold.querySelector('.match-count');
1434
+ if (foldBadge) {
1435
+ foldBadge.textContent = String(total);
1436
+ foldBadge.style.display = total > 0 ? 'inline-block' : 'none';
1437
+ }
1438
+ });
1314
1439
  }
1315
1440
 
1316
1441
  async function saveSettings() {
@@ -1682,7 +1807,7 @@ async function removeProject(name) {
1682
1807
  }
1683
1808
  }
1684
1809
 
1685
- window.MinionsSettings = { openSettings, saveSettings, addProject, removeProject, resetSettingsToDefaults, _selectSettingsTab, _applySettingsSearch };
1810
+ window.MinionsSettings = { openSettings, saveSettings, addProject, removeProject, resetSettingsToDefaults, _selectSettingsTab, _applySettingsSearch, _toggleSettingsFold };
1686
1811
 
1687
1812
  // ── Settings embed mode (Slim UX parity — W-mrkpke07000i8ed5) ───────────
1688
1813
  // The Slim UX Settings modal embeds this LITERAL classic Settings surface in
@@ -1308,6 +1308,19 @@
1308
1308
  .settings-rail-btn.active { color: var(--blue); border-left-color: var(--blue); background: var(--surface); font-weight: 600; }
1309
1309
  .settings-rail-btn.no-match { opacity: 0.3; }
1310
1310
  .settings-rail-btn .match-count { font-size: var(--text-xs); color: var(--blue); background: var(--bg); border-radius: var(--radius-xl); padding: 1px 6px; min-width: 18px; text-align: center; }
1311
+ /* W-mryz3r7h — collapsible rail fold ("Advanced"). The fold header is a real
1312
+ <button> (keyboard-operable); child rail buttons live in a container that
1313
+ is hidden unless the fold is .open. Children are indented under the header. */
1314
+ .settings-rail-fold { display: flex; align-items: center; gap: var(--space-2); width: 100%; padding: var(--space-3) var(--space-6); background: transparent; border: none; border-left: 3px solid transparent; color: var(--muted); font-size: var(--text-md); font-weight: 400; font-family: inherit; cursor: pointer; text-align: left; transition: all var(--transition-fast); }
1315
+ .settings-rail-fold:hover { color: var(--text); background: var(--surface); }
1316
+ .settings-rail-fold.no-match { opacity: 0.3; }
1317
+ .settings-rail-fold .rail-label { flex: 1; }
1318
+ .settings-rail-fold .match-count { font-size: var(--text-xs); color: var(--blue); background: var(--bg); border-radius: var(--radius-xl); padding: 1px 6px; min-width: 18px; text-align: center; }
1319
+ .settings-rail-fold .fold-chevron::before { content: '\25B8'; display: inline-block; font-size: var(--text-xs); color: var(--muted); transition: transform var(--transition-fast); }
1320
+ .settings-rail-fold.open .fold-chevron::before { transform: rotate(90deg); }
1321
+ .settings-rail-group { display: none; }
1322
+ .settings-rail-group.open { display: block; }
1323
+ .settings-rail-btn--child { padding-left: var(--space-9); }
1311
1324
  .settings-content { flex: 1; overflow-y: auto; padding: var(--space-7) var(--space-8); min-width: 0; }
1312
1325
  .settings-pane { display: none; }
1313
1326
  .settings-pane.active { display: block; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2414",
3
+ "version": "0.1.2415",
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"