@yemi33/minions 0.1.2440 → 0.1.2441

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.
@@ -202,24 +202,24 @@
202
202
  </div>
203
203
  </div>
204
204
 
205
- <!-- Knowledge control panel — opened from the "Knowledge" status tile. One
206
- unified box surfacing all three knowledge surfaces via tabs: Pinned
207
- Context, Notes, and KB (knowledge base). Each tab is rendered lazily by
208
- dashboard/slim/js/knowledge.js into #slim-knowledge-body. Backed by the
209
- existing endpoints (no new server routes):
205
+ <!-- Knowledge control panel — opened from the "Knowledge" status tile. Two
206
+ tabs: Pinned Context (slim-native, via pinned.js there is no standalone
207
+ classic route for pinned, it lives on /home) and Notes & Knowledge, which
208
+ embeds the LITERAL classic /inbox screen (Notes Inbox + Team Notes +
209
+ Knowledge Base + memory search) in a chrome-off /inbox?embed=1 iframe
210
+ rather than a slim-native fork (CLAUDE.md "Slim UX vs Classic UX — reuse,
211
+ don't fork"). Rendered lazily by dashboard/slim/js/knowledge.js into
212
+ #slim-knowledge-body. Backed by the existing endpoints (no new routes):
210
213
  Pinned — /api/pinned (+ update/remove), surfaced via /api/status.
211
- Notes — /api/notes-full (read), /api/notes-save (edit notes.md),
212
- /api/notes (add an inbox note).
213
- KB — /api/knowledge (list), /api/knowledge/:cat/:file (read),
214
- /api/knowledge POST (create), /api/kb-pins(+/toggle). -->
214
+ Notes & Knowledge the classic /inbox page (/api/notes*, /api/knowledge,
215
+ /api/kb-pins, memory search) inside the embedded iframe. -->
215
216
  <div class="modal-bg" id="slim-knowledge-modal">
216
217
  <div class="modal slim-knowledge-modal-inner">
217
218
  <div class="modal-header">
218
219
  <h3>Knowledge</h3>
219
220
  <div class="kn-tabs" id="slim-kn-tabs" role="tablist">
220
221
  <button class="kn-tab active" id="slim-kn-tab-pinned" data-kn-tab="pinned" type="button" role="tab" aria-selected="true" aria-controls="slim-knowledge-body">Pinned Context</button>
221
- <button class="kn-tab" id="slim-kn-tab-notes" data-kn-tab="notes" type="button" role="tab" aria-selected="false" aria-controls="slim-knowledge-body">Notes</button>
222
- <button class="kn-tab" id="slim-kn-tab-kb" data-kn-tab="kb" type="button" role="tab" aria-selected="false" aria-controls="slim-knowledge-body">KB</button>
222
+ <button class="kn-tab" id="slim-kn-tab-inbox" data-kn-tab="inbox" type="button" role="tab" aria-selected="false" aria-controls="slim-knowledge-body">Notes &amp; Knowledge</button>
223
223
  </div>
224
224
  <button id="slim-knowledge-close" class="icon-btn" title="Close" style="margin-left:auto">&times;</button>
225
225
  </div>
@@ -1,34 +1,23 @@
1
1
 
2
- // ── Knowledge control panel (Pinned Context + Notes + KB) ──────────
3
- // One unified cockpit box over the three knowledge surfaces, replacing the
4
- // old single "Pinned context" tile. Reuses the classic dashboard's existing
5
- // endpoints (no new server routes):
6
- // Pinned /api/pinned (+ update/remove); the Pinned-Context tab reuses the
7
- // editor + list renderer in pinned.js.
8
- // Notes /api/notes-full (read notes.md), /api/notes-save (edit notes.md),
9
- // /api/notes (add an inbox note for consolidation).
10
- // KB — /api/knowledge (list), /api/knowledge/:cat/:file (read entry),
11
- // /api/knowledge POST (create), /api/kb-pins(+/toggle) (pin state).
2
+ // ── Knowledge control panel (Pinned Context + Notes & Knowledge) ────
3
+ // One cockpit box over the knowledge surfaces, replacing the old single
4
+ // "Pinned context" tile. Two tabs:
5
+ // Pinned slim-native (there is no standalone classic route for pinned;
6
+ // it lives on /home). Reuses the editor + list renderer in
7
+ // pinned.js, backed by /api/pinned (+ update/remove).
8
+ // Notes & Knowledge embeds the LITERAL classic /inbox screen (Notes
9
+ // Inbox + Team Notes + Knowledge Base + memory search) in a
10
+ // chrome-off /inbox?embed=1 iframe, per CLAUDE.md "Slim UX vs
11
+ // Classic UX — reuse, don't fork". Zero forked rendering logic.
12
12
  //
13
13
  // Counts: pinned is live from the 5s status poll (renderKnowledgeTile is fed
14
14
  // by applyStatus). Notes + KB counts are fetched lazily — once shortly after
15
- // load and again after any create/save — so the heavier /api/notes-full and
15
+ // load and again on modal open — so the heavier /api/notes-full and
16
16
  // /api/knowledge reads stay off the recurring poll.
17
17
 
18
18
  var _knowledgeCounts = { pinned: 0, notes: null, kb: null };
19
19
  var _knActiveTab = 'pinned';
20
- var _kbData = null; // cached /api/knowledge payload
21
- var _kbPins = []; // cached pinned KB keys ('knowledge/<cat>/<file>')
22
-
23
- var KB_CAT_LABELS = {
24
- architecture: 'Architecture', conventions: 'Conventions',
25
- 'project-notes': 'Project Notes', 'build-reports': 'Build Reports',
26
- reviews: 'Reviews', learnings: 'Learnings', decisions: 'Decisions',
27
- incidents: 'Incidents', 'api-notes': 'API Notes',
28
- };
29
- // Categories offered in the KB create form (mirrors the classic dashboard).
30
- var KB_CREATE_CATS = ['architecture', 'conventions', 'project-notes', 'build-reports', 'reviews'];
31
- var KB_LIST_CAP = 60; // cap rendered rows; the KB can hold thousands
20
+ var _knInboxFrame = null; // cached /inbox?embed=1 iframe (created once)
32
21
 
33
22
  // ── Cockpit tile ───────────────────────────────────────────────────
34
23
  // Called from applyStatus with the live pinned count each poll, and from the
@@ -68,35 +57,24 @@
68
57
  try {
69
58
  var kres = await fetch('/api/knowledge', { headers: { 'Accept': 'application/json' } });
70
59
  if (kres.ok) {
71
- _kbData = await kres.json();
72
- _knowledgeCounts.kb = _countKbEntries(_kbData);
60
+ _knowledgeCounts.kb = _countKbEntries(await kres.json());
73
61
  }
74
62
  } catch (e) { /* keep last-known */ }
75
63
  renderKnowledgeTile();
76
64
  }
77
65
 
78
- async function loadKbPins() {
79
- try {
80
- var res = await fetch('/api/kb-pins', { headers: { 'Accept': 'application/json' } });
81
- if (!res.ok) return;
82
- var d = await res.json();
83
- _kbPins = Array.isArray(d.pins) ? d.pins : [];
84
- } catch (e) { _kbPins = _kbPins || []; }
85
- }
86
-
87
- function kbPinKey(cat, file) { return 'knowledge/' + cat + '/' + file; }
88
- function isKbPinned(key) { return _kbPins.indexOf(key) !== -1; }
89
-
90
66
  // ── Modal open/close + tab switching ───────────────────────────────
91
67
  function openKnowledgeModal() {
92
68
  var modal = document.getElementById('slim-knowledge-modal');
93
69
  if (!modal) return;
94
- renderKnowledgeTab();
70
+ renderKnowledgeTab(); // posts visibility=true to the embed if the inbox tab is active
95
71
  modal.classList.add('open');
96
72
  }
97
73
  function closeKnowledgeModal() {
98
74
  var modal = document.getElementById('slim-knowledge-modal');
99
75
  if (modal) modal.classList.remove('open');
76
+ // Suspend the embedded /inbox poll while the modal is closed.
77
+ _postKnEmbedVisibility(false);
100
78
  }
101
79
  function setKnowledgeTab(tab) {
102
80
  _knActiveTab = tab;
@@ -112,13 +90,81 @@
112
90
  if (body && activeTabId) body.setAttribute('aria-labelledby', activeTabId);
113
91
  renderKnowledgeTab();
114
92
  }
93
+ // Tab routing. The Pinned tab renders slim-native content into a transient
94
+ // container; the Notes & Knowledge tab shows a cached /inbox?embed=1 iframe
95
+ // (created once, kept mounted so a tab switch never reloads it).
115
96
  function renderKnowledgeTab() {
97
+ var c = _ensureKnContainers();
98
+ if (!c) return;
99
+ if (_knActiveTab === 'inbox') {
100
+ renderKnowledgeInboxTab();
101
+ } else {
102
+ c.host.hidden = true;
103
+ _postKnEmbedVisibility(false);
104
+ c.transient.textContent = '';
105
+ renderKnowledgePinnedTab(c.transient);
106
+ }
107
+ }
108
+
109
+ // Persistent containers inside #slim-knowledge-body: an embed host that holds
110
+ // the cached iframe (never wiped, so the iframe is never re-parented/reloaded)
111
+ // and a transient container for the Pinned tab's slim-native content. Built
112
+ // with createElement (no innerHTML) to satisfy the no-unsanitized lint gate.
113
+ function _ensureKnContainers() {
116
114
  var body = document.getElementById('slim-knowledge-body');
117
- if (!body) return;
118
- body.textContent = '';
119
- if (_knActiveTab === 'notes') renderKnowledgeNotesTab(body);
120
- else if (_knActiveTab === 'kb') renderKnowledgeKbTab(body);
121
- else renderKnowledgePinnedTab(body);
115
+ if (!body) return null;
116
+ var host = document.getElementById('slim-kn-embed-host');
117
+ if (!host) {
118
+ host = document.createElement('div');
119
+ host.id = 'slim-kn-embed-host';
120
+ host.className = 'slim-kn-embed-host';
121
+ host.hidden = true;
122
+ body.appendChild(host);
123
+ }
124
+ var transient = document.getElementById('slim-kn-transient');
125
+ if (!transient) {
126
+ transient = document.createElement('div');
127
+ transient.id = 'slim-kn-transient';
128
+ transient.className = 'slim-kn-transient';
129
+ body.appendChild(transient);
130
+ }
131
+ return { host: host, transient: transient };
132
+ }
133
+
134
+ // Tell the embedded classic /inbox SPA whether its iframe is visible so it can
135
+ // suspend/resume its own /api/status poll (dashboard/js/refresh.js listens for
136
+ // this exact message shape). Same-origin, guarded defensively.
137
+ function _postKnEmbedVisibility(visible) {
138
+ try {
139
+ if (_knInboxFrame && _knInboxFrame.contentWindow) {
140
+ _knInboxFrame.contentWindow.postMessage(
141
+ { source: 'minions-slim-embed', type: 'visibility', visible: !!visible }, '*');
142
+ }
143
+ } catch (e) { /* defensive — same-origin means this should never throw */ }
144
+ }
145
+
146
+ // ── Tab: Notes & Knowledge (classic /inbox embed) ──────────────────
147
+ // Lazily creates ONE cached iframe pointing at the classic Notes+KB screen
148
+ // (/inbox?embed=1) and mounts it into the persistent embed host. Subsequent
149
+ // activations just re-show the same iframe (never recreated), so switching
150
+ // tabs never reloads it.
151
+ function renderKnowledgeInboxTab() {
152
+ var c = _ensureKnContainers();
153
+ if (!c) return;
154
+ c.transient.textContent = '';
155
+ if (!_knInboxFrame) {
156
+ var frame = document.createElement('iframe');
157
+ frame.className = 'slim-kn-embed';
158
+ frame.src = '/inbox?embed=1';
159
+ frame.title = 'Notes & Knowledge';
160
+ frame.addEventListener('load', function() {
161
+ _postKnEmbedVisibility(_knActiveTab === 'inbox');
162
+ });
163
+ c.host.appendChild(frame);
164
+ _knInboxFrame = frame;
165
+ }
166
+ c.host.hidden = false;
167
+ _postKnEmbedVisibility(true);
122
168
  }
123
169
 
124
170
  // ── Tab: Pinned Context ────────────────────────────────────────────
@@ -143,423 +189,10 @@
143
189
  renderSlimPinnedList(); // defined in pinned.js; renders into #slim-pinned-list
144
190
  }
145
191
 
146
- // ── Tab: Notes ─────────────────────────────────────────────────────
147
- function renderKnowledgeNotesTab(body) {
148
- var intro = document.createElement('p');
149
- intro.textContent = 'Consolidated team notes (notes.md). Edit and save, or add a note for the next consolidation sweep.';
150
- body.appendChild(intro);
151
-
152
- var msg = document.createElement('div');
153
- msg.id = 'slim-notes-msg';
154
- msg.className = 'kn-msg';
155
- body.appendChild(msg);
156
-
157
- var ta = document.createElement('textarea');
158
- ta.id = 'slim-notes-content';
159
- ta.className = 'linkpr-input kn-notes-textarea';
160
- ta.rows = 12;
161
- ta.value = 'Loading notes…';
162
- ta.disabled = true;
163
- body.appendChild(ta);
164
-
165
- var saveRow = document.createElement('div');
166
- saveRow.className = 'kn-toolbar kn-toolbar-end';
167
- var saveBtn = document.createElement('button');
168
- saveBtn.className = 'btn-primary';
169
- saveBtn.type = 'button';
170
- saveBtn.textContent = 'Save notes.md';
171
- saveBtn.disabled = true;
172
- saveBtn.addEventListener('click', saveKnowledgeNotes);
173
- saveRow.appendChild(saveBtn);
174
- body.appendChild(saveRow);
175
-
176
- // Add-a-note (inbox) sub-form.
177
- var addHead = document.createElement('div');
178
- addHead.className = 'kn-section-head';
179
- addHead.textContent = 'Add a note for consolidation';
180
- body.appendChild(addHead);
181
-
182
- var titleLabel = document.createElement('label');
183
- titleLabel.className = 'linkpr-label';
184
- titleLabel.textContent = 'Title';
185
- var titleInput = document.createElement('input');
186
- titleInput.id = 'slim-note-title';
187
- titleInput.className = 'linkpr-input';
188
- titleInput.type = 'text';
189
- titleInput.placeholder = 'Short summary';
190
- titleLabel.appendChild(titleInput);
191
- body.appendChild(titleLabel);
192
-
193
- var whatLabel = document.createElement('label');
194
- whatLabel.className = 'linkpr-label';
195
- whatLabel.textContent = 'Note';
196
- var whatInput = document.createElement('textarea');
197
- whatInput.id = 'slim-note-what';
198
- whatInput.className = 'linkpr-input';
199
- whatInput.rows = 3;
200
- whatInput.placeholder = 'What did you learn / decide?';
201
- whatLabel.appendChild(whatInput);
202
- body.appendChild(whatLabel);
203
-
204
- var addRow = document.createElement('div');
205
- addRow.className = 'kn-toolbar kn-toolbar-end';
206
- var addNoteMsg = document.createElement('span');
207
- addNoteMsg.id = 'slim-note-msg';
208
- addNoteMsg.className = 'kn-msg kn-msg-inline';
209
- addRow.appendChild(addNoteMsg);
210
- var addNoteBtn = document.createElement('button');
211
- addNoteBtn.className = 'btn-secondary';
212
- addNoteBtn.type = 'button';
213
- addNoteBtn.textContent = 'Add note';
214
- addNoteBtn.addEventListener('click', submitKnowledgeNote);
215
- addRow.appendChild(addNoteBtn);
216
- body.appendChild(addRow);
217
-
218
- // Load notes.md into the textarea.
219
- fetch('/api/notes-full', { headers: { 'Accept': 'text/plain' } })
220
- .then(function(r) { return r.ok ? r.text() : Promise.reject(new Error('HTTP ' + r.status)); })
221
- .then(function(text) {
222
- if (document.getElementById('slim-notes-content') !== ta) return; // tab switched away
223
- ta.value = (text === 'No notes file found.') ? '' : text;
224
- ta.disabled = false;
225
- saveBtn.disabled = false;
226
- _knowledgeCounts.notes = _countNotesEntries(text);
227
- renderKnowledgeTile();
228
- })
229
- .catch(function(e) {
230
- ta.value = '';
231
- ta.disabled = false;
232
- saveBtn.disabled = false;
233
- if (msg) { msg.style.color = 'var(--red)'; msg.textContent = 'Could not load notes: ' + (e.message || e); }
234
- });
235
- }
236
-
237
- async function saveKnowledgeNotes() {
238
- var ta = document.getElementById('slim-notes-content');
239
- var msg = document.getElementById('slim-notes-msg');
240
- if (!ta) return;
241
- if (msg) { msg.style.color = 'var(--muted)'; msg.textContent = 'Saving…'; }
242
- try {
243
- var res = await fetch('/api/notes-save', {
244
- method: 'POST', headers: { 'Content-Type': 'application/json' },
245
- body: JSON.stringify({ content: ta.value, file: 'notes.md' }),
246
- });
247
- var d = await res.json().catch(function() { return {}; });
248
- if (!res.ok) throw new Error(d.error || ('HTTP ' + res.status));
249
- if (msg) { msg.style.color = 'var(--green)'; msg.textContent = 'Saved.'; }
250
- _knowledgeCounts.notes = _countNotesEntries(ta.value);
251
- renderKnowledgeTile();
252
- } catch (e) {
253
- if (msg) { msg.style.color = 'var(--red)'; msg.textContent = 'Error: ' + (e && e.message ? e.message : 'failed'); }
254
- }
255
- }
256
-
257
- async function submitKnowledgeNote() {
258
- var titleEl = document.getElementById('slim-note-title');
259
- var whatEl = document.getElementById('slim-note-what');
260
- var msg = document.getElementById('slim-note-msg');
261
- var title = titleEl ? (titleEl.value || '').trim() : '';
262
- var what = whatEl ? (whatEl.value || '').trim() : '';
263
- if (!title) {
264
- if (msg) { msg.style.color = 'var(--red)'; msg.textContent = 'Title is required.'; }
265
- return;
266
- }
267
- if (msg) { msg.style.color = 'var(--muted)'; msg.textContent = 'Adding…'; }
268
- try {
269
- var res = await fetch('/api/notes', {
270
- method: 'POST', headers: { 'Content-Type': 'application/json' },
271
- body: JSON.stringify({ title: title, what: what }),
272
- });
273
- var d = await res.json().catch(function() { return {}; });
274
- if (!res.ok) throw new Error(d.error || ('HTTP ' + res.status));
275
- if (titleEl) titleEl.value = '';
276
- if (whatEl) whatEl.value = '';
277
- if (msg) { msg.style.color = 'var(--green)'; msg.textContent = 'Note added for consolidation.'; }
278
- } catch (e) {
279
- if (msg) { msg.style.color = 'var(--red)'; msg.textContent = 'Error: ' + (e && e.message ? e.message : 'failed'); }
280
- }
281
- }
282
-
283
- // ── Tab: KB ────────────────────────────────────────────────────────
284
- function renderKnowledgeKbTab(body) {
285
- var intro = document.createElement('p');
286
- intro.textContent = 'Knowledge base — categorized entries written by consolidation. Click an entry to read it.';
287
- body.appendChild(intro);
288
-
289
- var toolbar = document.createElement('div');
290
- toolbar.className = 'kn-toolbar';
291
- var newBtn = document.createElement('button');
292
- newBtn.className = 'linkpr-chip';
293
- newBtn.type = 'button';
294
- newBtn.textContent = '+ New entry';
295
- newBtn.addEventListener('click', function() { toggleKbCreateForm(body); });
296
- toolbar.appendChild(newBtn);
297
- body.appendChild(toolbar);
298
-
299
- var listMsg = document.createElement('div');
300
- listMsg.id = 'slim-kb-list-msg';
301
- listMsg.className = 'kn-msg';
302
- body.appendChild(listMsg);
303
-
304
- var listWrap = document.createElement('div');
305
- listWrap.id = 'slim-kb-list';
306
- body.appendChild(listWrap);
307
-
308
- if (_kbData) renderKbList(listWrap);
309
- else {
310
- var loading = document.createElement('div');
311
- loading.className = 'tile-empty';
312
- loading.textContent = 'Loading knowledge base…';
313
- listWrap.appendChild(loading);
314
- }
315
- // Always refresh list + pins on open so newly-swept entries appear.
316
- Promise.all([
317
- fetch('/api/knowledge', { headers: { 'Accept': 'application/json' } }).then(function(r) { return r.ok ? r.json() : null; }).catch(function() { return null; }),
318
- loadKbPins(),
319
- ]).then(function(results) {
320
- if (results[0]) {
321
- _kbData = results[0];
322
- _knowledgeCounts.kb = _countKbEntries(_kbData);
323
- renderKnowledgeTile();
324
- }
325
- if (_knActiveTab === 'kb') {
326
- var lw = document.getElementById('slim-kb-list');
327
- if (lw) renderKbList(lw);
328
- }
329
- });
330
- }
331
-
332
- function _kbAllItems() {
333
- var items = [];
334
- if (!_kbData) return items;
335
- Object.keys(_kbData).forEach(function(cat) {
336
- if (!Array.isArray(_kbData[cat])) return;
337
- _kbData[cat].forEach(function(item) {
338
- items.push({ file: item.file, category: cat, title: item.title, agent: item.agent, date: item.date, sortTs: item.sortTs, preview: item.preview });
339
- });
340
- });
341
- return items;
342
- }
343
-
344
- function renderKbList(listWrap) {
345
- listWrap.textContent = '';
346
- var items = _kbAllItems();
347
- if (!items.length) {
348
- var empty = document.createElement('div');
349
- empty.className = 'tile-empty';
350
- empty.textContent = 'No knowledge entries yet. Notes are classified here after consolidation.';
351
- listWrap.appendChild(empty);
352
- return;
353
- }
354
- // Pinned first, then newest.
355
- items.sort(function(a, b) {
356
- var ap = isKbPinned(kbPinKey(a.category, a.file));
357
- var bp = isKbPinned(kbPinKey(b.category, b.file));
358
- if (ap !== bp) return ap ? -1 : 1;
359
- return (b.sortTs || 0) - (a.sortTs || 0) || (b.date || '').localeCompare(a.date || '');
360
- });
361
- var shown = items.slice(0, KB_LIST_CAP);
362
- shown.forEach(function(item) {
363
- listWrap.appendChild(buildKbRow(item));
364
- });
365
- if (items.length > shown.length) {
366
- var more = document.createElement('div');
367
- more.className = 'tile-empty';
368
- more.textContent = 'Showing ' + shown.length + ' of ' + items.length + ' entries (newest + pinned first).';
369
- listWrap.appendChild(more);
370
- }
371
- }
372
-
373
- function buildKbRow(item) {
374
- var key = kbPinKey(item.category, item.file);
375
- var pinned = isKbPinned(key);
376
- var row = document.createElement('div');
377
- row.className = 'kb-row' + (pinned ? ' kb-row-pinned' : '');
378
-
379
- var top = document.createElement('div');
380
- top.className = 'kb-row-top';
381
-
382
- var pinBtn = document.createElement('button');
383
- pinBtn.className = 'kb-pin-btn' + (pinned ? ' on' : '');
384
- pinBtn.type = 'button';
385
- pinBtn.title = pinned ? 'Unpin from KB' : 'Pin in KB';
386
- pinBtn.textContent = pinned ? '★' : '☆';
387
- pinBtn.addEventListener('click', function(ev) { ev.stopPropagation(); toggleKbPin(key); });
388
- top.appendChild(pinBtn);
389
-
390
- var title = document.createElement('span');
391
- title.className = 'kb-row-title';
392
- title.textContent = item.title || item.file || '(untitled)';
393
- title.title = item.title || '';
394
- top.appendChild(title);
395
- row.appendChild(top);
396
-
397
- var meta = document.createElement('div');
398
- meta.className = 'kb-row-meta';
399
- meta.textContent = [KB_CAT_LABELS[item.category] || item.category, item.agent || null, item.date || null]
400
- .filter(Boolean).join(' · ');
401
- row.appendChild(meta);
402
-
403
- if (item.preview) {
404
- var prev = document.createElement('div');
405
- prev.className = 'kb-row-preview';
406
- var t = String(item.preview);
407
- prev.textContent = t.length > 200 ? t.slice(0, 200) + '…' : t;
408
- row.appendChild(prev);
409
- }
410
-
411
- row.addEventListener('click', function() { openKbItem(item.category, item.file); });
412
- return row;
413
- }
414
-
415
- async function toggleKbPin(key) {
416
- var listMsg = document.getElementById('slim-kb-list-msg');
417
- if (listMsg) { listMsg.style.color = ''; listMsg.textContent = ''; }
418
- try {
419
- var res = await fetch('/api/kb-pins/toggle', {
420
- method: 'POST', headers: { 'Content-Type': 'application/json' },
421
- body: JSON.stringify({ key: key }),
422
- });
423
- var d = await res.json().catch(function() { return {}; });
424
- if (!res.ok) throw new Error(d.error || ('HTTP ' + res.status));
425
- if (d.pinned) { if (_kbPins.indexOf(key) === -1) _kbPins.push(key); }
426
- else { _kbPins = _kbPins.filter(function(k) { return k !== key; }); }
427
- var lw = document.getElementById('slim-kb-list');
428
- if (lw) renderKbList(lw);
429
- } catch (e) {
430
- if (listMsg) { listMsg.style.color = 'var(--red)'; listMsg.textContent = 'Pin toggle failed: ' + (e && e.message ? e.message : e); }
431
- }
432
- }
433
-
434
- async function openKbItem(category, file) {
435
- var body = document.getElementById('slim-knowledge-body');
436
- if (!body) return;
437
- body.textContent = '';
438
- var backRow = document.createElement('div');
439
- backRow.className = 'kn-toolbar';
440
- var backBtn = document.createElement('button');
441
- backBtn.className = 'btn-secondary';
442
- backBtn.type = 'button';
443
- backBtn.textContent = '← Back to list';
444
- backBtn.addEventListener('click', function() { renderKnowledgeTab(); });
445
- backRow.appendChild(backBtn);
446
- body.appendChild(backRow);
447
-
448
- var head = document.createElement('div');
449
- head.className = 'kn-section-head';
450
- head.textContent = file;
451
- body.appendChild(head);
452
-
453
- var pre = document.createElement('pre');
454
- pre.className = 'kb-entry-content';
455
- pre.textContent = 'Loading…';
456
- body.appendChild(pre);
457
-
458
- try {
459
- var res = await fetch('/api/knowledge/' + encodeURIComponent(category) + '/' + encodeURIComponent(file));
460
- if (!res.ok) throw new Error('HTTP ' + res.status);
461
- var text = await res.text();
462
- pre.textContent = text.replace(/^---[\s\S]*?---\n*/m, '');
463
- } catch (e) {
464
- pre.textContent = 'Failed to load entry: ' + (e && e.message ? e.message : e);
465
- }
466
- }
467
-
468
- function toggleKbCreateForm(body) {
469
- var existing = document.getElementById('slim-kb-create');
470
- if (existing) { existing.parentNode.removeChild(existing); return; }
471
- var form = document.createElement('div');
472
- form.id = 'slim-kb-create';
473
- form.className = 'kn-create-form';
474
-
475
- var catLabel = document.createElement('label');
476
- catLabel.className = 'linkpr-label';
477
- catLabel.textContent = 'Category';
478
- var catSel = document.createElement('select');
479
- catSel.id = 'slim-kb-category';
480
- catSel.className = 'linkpr-input';
481
- KB_CREATE_CATS.forEach(function(c) {
482
- var opt = document.createElement('option');
483
- opt.value = c;
484
- opt.textContent = KB_CAT_LABELS[c] || c;
485
- catSel.appendChild(opt);
486
- });
487
- catLabel.appendChild(catSel);
488
- form.appendChild(catLabel);
489
-
490
- var titleLabel = document.createElement('label');
491
- titleLabel.className = 'linkpr-label';
492
- titleLabel.textContent = 'Title';
493
- var titleInput = document.createElement('input');
494
- titleInput.id = 'slim-kb-title';
495
- titleInput.className = 'linkpr-input';
496
- titleInput.type = 'text';
497
- titleInput.placeholder = 'Entry title';
498
- titleLabel.appendChild(titleInput);
499
- form.appendChild(titleLabel);
500
-
501
- var contentLabel = document.createElement('label');
502
- contentLabel.className = 'linkpr-label';
503
- contentLabel.textContent = 'Content';
504
- var contentInput = document.createElement('textarea');
505
- contentInput.id = 'slim-kb-content';
506
- contentInput.className = 'linkpr-input';
507
- contentInput.rows = 6;
508
- contentInput.placeholder = 'Write your knowledge entry…';
509
- contentLabel.appendChild(contentInput);
510
- form.appendChild(contentLabel);
511
-
512
- var row = document.createElement('div');
513
- row.className = 'kn-toolbar kn-toolbar-end';
514
- var msg = document.createElement('span');
515
- msg.id = 'slim-kb-create-msg';
516
- msg.className = 'kn-msg kn-msg-inline';
517
- row.appendChild(msg);
518
- var saveBtn = document.createElement('button');
519
- saveBtn.className = 'btn-primary';
520
- saveBtn.type = 'button';
521
- saveBtn.textContent = 'Save entry';
522
- saveBtn.addEventListener('click', submitKbEntry);
523
- row.appendChild(saveBtn);
524
- form.appendChild(row);
525
-
526
- // Insert the form directly after the toolbar (before the list).
527
- var listWrap = document.getElementById('slim-kb-list');
528
- if (listWrap && listWrap.parentNode) listWrap.parentNode.insertBefore(form, listWrap);
529
- else body.appendChild(form);
530
- }
531
-
532
- async function submitKbEntry() {
533
- var catEl = document.getElementById('slim-kb-category');
534
- var titleEl = document.getElementById('slim-kb-title');
535
- var contentEl = document.getElementById('slim-kb-content');
536
- var msg = document.getElementById('slim-kb-create-msg');
537
- var category = catEl ? catEl.value : '';
538
- var title = titleEl ? (titleEl.value || '').trim() : '';
539
- var content = contentEl ? contentEl.value : '';
540
- if (!title || !content.trim()) {
541
- if (msg) { msg.style.color = 'var(--red)'; msg.textContent = 'Title and content are required.'; }
542
- return;
543
- }
544
- if (msg) { msg.style.color = 'var(--muted)'; msg.textContent = 'Saving…'; }
545
- try {
546
- var res = await fetch('/api/knowledge', {
547
- method: 'POST', headers: { 'Content-Type': 'application/json' },
548
- body: JSON.stringify({ category: category, title: title, content: content }),
549
- });
550
- var d = await res.json().catch(function() { return {}; });
551
- if (!res.ok) throw new Error(d.error || ('HTTP ' + res.status));
552
- // Refresh the list + count, then re-render the KB tab.
553
- var kres = await fetch('/api/knowledge', { headers: { 'Accept': 'application/json' } });
554
- if (kres.ok) { _kbData = await kres.json(); _knowledgeCounts.kb = _countKbEntries(_kbData); renderKnowledgeTile(); }
555
- if (_knActiveTab === 'kb') renderKnowledgeTab();
556
- } catch (e) {
557
- if (msg) { msg.style.color = 'var(--red)'; msg.textContent = 'Error: ' + (e && e.message ? e.message : 'failed'); }
558
- }
559
- }
560
-
561
192
  // ── Wiring ─────────────────────────────────────────────────────────
562
- bindModalClose('slim-knowledge-modal', 'slim-knowledge-close');
193
+ // onClose fires on every dismiss path (close-X, backdrop, Esc) so the
194
+ // embedded /inbox poll is suspended whenever the modal closes.
195
+ bindModalClose('slim-knowledge-modal', 'slim-knowledge-close', function() { _postKnEmbedVisibility(false); });
563
196
  (function bindKnowledgeUi() {
564
197
  var tabBar = document.getElementById('slim-kn-tabs');
565
198
  if (tabBar) {
@@ -873,8 +873,9 @@
873
873
  .pinned-row-actions .btn-secondary { padding: 4px 12px; font-size: var(--text-base); }
874
874
  .pinned-row-unpin { color: var(--red); border-color: var(--red); }
875
875
 
876
- /* Knowledge control panel (slim-knowledge-modal): Pinned Context / Notes /
877
- KB tabs in one box. Reuses .tile-* and .pinned-row primitives. */
876
+ /* Knowledge control panel (slim-knowledge-modal): Pinned Context (slim-
877
+ native) + Notes & Knowledge (embedded classic /inbox) tabs in one box.
878
+ Reuses .tile-* and .pinned-row primitives. */
878
879
  .slim-knowledge-modal-inner { width: 720px; max-width: calc(100vw - 32px); }
879
880
  .kn-tabs { display: flex; gap: 6px; margin-left: 16px; }
880
881
  .kn-tab {
@@ -905,51 +906,13 @@
905
906
  }
906
907
  .kn-msg { font-size: var(--text-base); min-height: 16px; color: var(--muted); }
907
908
  .kn-msg-inline { margin-right: auto; }
908
- .kn-notes-textarea { font-family: var(--mono, monospace); white-space: pre; }
909
- .kn-create-form {
910
- border: 1px solid var(--border);
911
- border-radius: var(--radius);
912
- background: var(--surface2);
913
- padding: 12px;
914
- margin-bottom: 12px;
915
- }
916
- /* KB list rows */
917
- .kb-row {
918
- border: 1px solid var(--border);
919
- border-radius: var(--radius);
920
- background: var(--surface2);
921
- padding: 8px 10px;
922
- margin-bottom: 8px;
923
- cursor: pointer;
924
- }
925
- .kb-row:hover { border-color: var(--blue); }
926
- .kb-row-pinned { border-color: var(--amber); }
927
- .kb-row-top { display: flex; align-items: center; gap: 8px; }
928
- .kb-row-title {
929
- flex: 1; min-width: 0;
930
- font-weight: 600; font-size: var(--text-md); color: var(--text);
931
- overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
932
- }
933
- .kb-pin-btn {
934
- flex: 0 0 auto;
935
- border: none; background: transparent; cursor: pointer;
936
- color: var(--muted); font-size: var(--text-lg); line-height: 1; padding: 0 2px;
937
- }
938
- .kb-pin-btn.on { color: var(--amber); }
939
- .kb-pin-btn:hover { color: var(--amber); }
940
- .kb-row-meta { font-size: var(--text-base); color: var(--muted); margin-top: 3px; }
941
- .kb-row-preview {
942
- margin-top: 5px;
943
- font-size: var(--text-base); color: var(--muted); line-height: 1.5;
944
- word-break: break-word;
945
- }
946
- .kb-entry-content {
947
- white-space: pre-wrap; word-break: break-word;
948
- font-family: var(--mono, monospace); font-size: var(--text-base);
949
- color: var(--text); line-height: 1.55;
950
- background: var(--surface2); border: 1px solid var(--border);
951
- border-radius: var(--radius); padding: 12px; margin: 0;
952
- max-height: 60vh; overflow-y: auto;
909
+ /* Notes & Knowledge tab: the embedded classic /inbox screen. The host div
910
+ is toggled via the [hidden] attribute — a plain div defaults to
911
+ display:block when shown and the UA [hidden] rule forces display:none
912
+ when hidden, so no extra host rule is needed. */
913
+ .slim-kn-embed {
914
+ display: block; width: 100%; height: 62vh; min-height: 360px;
915
+ border: 0; background: var(--bg);
953
916
  }
954
917
 
955
918
  .chat-thinking {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2440",
3
+ "version": "0.1.2441",
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"