@yemi33/minions 0.1.2255 → 0.1.2256

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.
@@ -1,9 +1,8 @@
1
1
  // Project context picker: populated from /api/status (which already returns
2
- // the configured project list). One-or-more projects: a <select> dropdown
3
- // whose first option is a "Select project" sentinel (value ""), italicized
4
- // via CSS, so the indicator reads "Select project" until the user explicitly
5
- // picks one and can always be cleared back to it. Zero-project: a hint to
6
- // add one. No auto-default: we never silently fall back to projects[0] /
2
+ // the configured project list). One-or-more projects: a row of selectable pill
3
+ // buttons, single-select, none active until the user explicitly picks one and
4
+ // can always be toggled back off to a "Select project" state. Zero-project: a
5
+ // hint to add one. No auto-default: we never silently fall back to projects[0] /
7
6
  // last-used / constellation, so project-scoped CC actions can't land in the
8
7
  // wrong project (W-mqayzsj3). Selection persists per-browser in localStorage
9
8
  // and is included in every /api/command-center/stream call.
@@ -26,29 +25,33 @@
26
25
  empty.textContent = 'No projects linked yet.';
27
26
  return empty;
28
27
  }
29
- // Strip variant: project <select>. The first option is a "Select project"
30
- // sentinel (value "") that is selected whenever no project is explicitly
31
- // chosen, so the indicator never auto-defaults and the user can always clear
32
- // their choice back to it. Returns the select so the caller can attach a
33
- // change listener.
34
- function makeContextSelect(projects, selected, displayByName) {
35
- var sel = document.createElement('select');
36
- sel.id = 'chat-context-select';
37
- sel.setAttribute('aria-label', 'Working in project');
38
- var placeholder = document.createElement('option');
39
- placeholder.value = '';
40
- placeholder.textContent = 'Select project';
41
- if (!selected) placeholder.selected = true;
42
- sel.appendChild(placeholder);
28
+ // Strip variant: project pills. One selectable pill per project, single-select.
29
+ // No auto-default: when nothing is explicitly chosen, no pill is active and the
30
+ // indicator stays in the "Select project" state, so the picker never silently
31
+ // falls back to projects[0] (W-mqayzsj3) and the user can always toggle their
32
+ // choice back off. Returns a container of pill buttons built as real DOM nodes
33
+ // (no innerHTML — SEC-03 ratchet, see test/unit.test.js DYNAMIC_INNERHTML_BASELINE);
34
+ // the caller attaches a click listener via event delegation.
35
+ function makeContextPills(projects, selected, displayByName) {
36
+ var group = document.createElement('span');
37
+ group.id = 'chat-context-pills';
38
+ group.className = 'context-pills';
39
+ group.setAttribute('role', 'radiogroup');
40
+ group.setAttribute('aria-label', 'Working in project');
43
41
  for (var i = 0; i < projects.length; i++) {
44
42
  var p = projects[i];
45
- var opt = document.createElement('option');
46
- opt.value = p;
47
- opt.textContent = (displayByName && displayByName[p]) || p;
48
- if (p === selected) opt.selected = true;
49
- sel.appendChild(opt);
43
+ var pill = document.createElement('button');
44
+ pill.type = 'button';
45
+ pill.className = 'context-pill';
46
+ pill.dataset.project = p;
47
+ pill.textContent = (displayByName && displayByName[p]) || p;
48
+ pill.setAttribute('role', 'radio');
49
+ var isActive = p === selected;
50
+ pill.classList.toggle('active', isActive);
51
+ pill.setAttribute('aria-checked', isActive ? 'true' : 'false');
52
+ group.appendChild(pill);
50
53
  }
51
- return sel;
54
+ return group;
52
55
  }
53
56
  async function loadProjectsAndRenderContext(opts) {
54
57
  var stripEl = document.getElementById('chat-context-strip');
@@ -77,23 +80,33 @@
77
80
  try { localStorage.removeItem(SLIM_PROJECT_KEY); } catch (_e) {}
78
81
  }
79
82
  // No auto-default: when nothing is explicitly selected, currentProject
80
- // stays null and the picker shows the "Select project" sentinel
81
- // (italicized via CSS). We deliberately do NOT fall back to projects[0]
82
- // here (W-mqayzsj3) so CC turns and project-scoped actions don't
83
- // silently target the wrong project.
84
- var sel = makeContextSelect(projects, currentProject, displayByName);
83
+ // stays null and no pill is active (the "Select project" state). We
84
+ // deliberately do NOT fall back to projects[0] here (W-mqayzsj3) so CC
85
+ // turns and project-scoped actions don't silently target the wrong
86
+ // project.
87
+ var pills = makeContextPills(projects, currentProject, displayByName);
85
88
  stripEl.style.display = '';
86
- controlsEl.replaceChildren(sel, makeContextAddBtn());
87
- sel.addEventListener('change', function(ev) {
88
- var val = ev.target.value || '';
89
- if (val) {
89
+ controlsEl.replaceChildren(pills, makeContextAddBtn());
90
+ pills.addEventListener('click', function(ev) {
91
+ var pill = ev.target.closest('.context-pill');
92
+ if (!pill || !pills.contains(pill)) return;
93
+ var val = pill.dataset.project || '';
94
+ if (val && val !== currentProject) {
95
+ // Select this project.
90
96
  currentProject = val;
91
97
  try { localStorage.setItem(SLIM_PROJECT_KEY, currentProject); } catch (_e) {}
92
98
  } else {
93
- // User cleared back to "Select project" — drop the persisted choice.
99
+ // Re-clicking the active pill toggles back to "Select project" —
100
+ // drop the persisted choice so nothing is selected.
94
101
  currentProject = null;
95
102
  try { localStorage.removeItem(SLIM_PROJECT_KEY); } catch (_e) {}
96
103
  }
104
+ var nodes = pills.querySelectorAll('.context-pill');
105
+ for (var j = 0; j < nodes.length; j++) {
106
+ var active = nodes[j].dataset.project === currentProject;
107
+ nodes[j].classList.toggle('active', active);
108
+ nodes[j].setAttribute('aria-checked', active ? 'true' : 'false');
109
+ }
97
110
  });
98
111
  }
99
112
  var addBtn = document.getElementById('chat-context-add');
@@ -496,27 +496,54 @@
496
496
  font-weight: 700;
497
497
  font-size: var(--text-base);
498
498
  }
499
- .chat-context-strip select {
499
+ /* Pills + "+ Add" lay out in a single row next to the "Working in" label
500
+ (W-mqsdptzy / PR #366). #chat-context-controls is a plain <span>, so
501
+ without an explicit flex the display:flex .context-pills (a block-level
502
+ flex container) pushed the "+ Add" sibling onto its own line below. */
503
+ .chat-context-strip #chat-context-controls {
504
+ display: flex;
505
+ align-items: center;
506
+ gap: var(--space-2);
507
+ flex-wrap: wrap;
508
+ min-width: 0;
509
+ }
510
+ .chat-context-strip .context-pills {
511
+ display: flex;
512
+ align-items: center;
513
+ gap: var(--space-2);
514
+ flex-wrap: wrap;
515
+ }
516
+ /* Project pills: outlined by default, filled/highlighted when active.
517
+ Single-select — exactly one (or zero) carries .active at a time. */
518
+ .chat-context-strip .context-pill {
500
519
  background: var(--bg);
501
520
  color: var(--text);
502
521
  border: 1px solid var(--border);
503
- border-radius: var(--radius);
504
- padding: 4px 8px;
522
+ border-radius: 999px;
523
+ padding: 4px 12px;
505
524
  font-family: inherit;
506
525
  font-size: var(--text-base);
526
+ line-height: 1;
507
527
  cursor: pointer;
528
+ transition: background 0.12s, border-color 0.12s, color 0.12s;
529
+ }
530
+ .chat-context-strip .context-pill:hover {
531
+ border-color: var(--blue);
532
+ background: var(--surface2);
533
+ }
534
+ .chat-context-strip .context-pill:focus-visible {
535
+ outline: none;
536
+ border-color: var(--blue);
537
+ }
538
+ .chat-context-strip .context-pill.active {
539
+ background: var(--blue);
540
+ border-color: var(--blue);
541
+ color: #fff;
542
+ font-weight: 600;
543
+ }
544
+ .chat-context-strip .context-pill.active:hover {
545
+ background: var(--blue);
508
546
  }
509
- .chat-context-strip select:focus { outline: none; border-color: var(--blue); }
510
- /* Italicize the "Select project" placeholder option AND the closed select
511
- widget while the placeholder is the active value. `:has()` is needed
512
- because native <select> rendering ignores child <option> styles for the
513
- displayed (closed) text — we have to style the <select> itself. The
514
- explicit `option { font-style: normal }` reset stops real project names
515
- from inheriting the parent select's italic when the dropdown is opened
516
- while the placeholder is still selected. */
517
- .chat-context-strip select option { font-style: normal; color: var(--text); }
518
- .chat-context-strip select option[value=""] { font-style: italic; color: var(--muted); }
519
- .chat-context-strip select:has(option[value=""]:checked) { font-style: italic; color: var(--muted); }
520
547
  .chat-context-strip .context-static {
521
548
  color: var(--text);
522
549
  font-weight: 500;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2255",
3
+ "version": "0.1.2256",
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"