mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-17 10:15:27 -04:00
Skills: dedupe by name + move Select to 2nd in kebab menu
- The API occasionally returns the same skill twice (built-in shadow vs user copy, or a write/read race) which made the duplicate-detector tag BOTH copies as the "recommended" keeper (the find-skills card showing duplicate #1 twice). Loading now filters out repeats by lowercased name before render. - Reordered the per-skill kebab menu: Publish/Unpublish → Select → Edit → Test → Audit → Delete. Select previously sat at the bottom; lifting it next to Publish puts the bulk-mode entry point with the other bulk-style action.
This commit is contained in:
+24
-14
@@ -91,7 +91,18 @@ export async function loadSkills(cascade = false) {
|
|||||||
try {
|
try {
|
||||||
const res = await fetch(`${API}/api/skills`);
|
const res = await fetch(`${API}/api/skills`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
skills = data.skills || [];
|
// Dedupe by name (case-insensitive) — the API has occasionally
|
||||||
|
// returned the same skill twice (built-in shadow + user copy, or
|
||||||
|
// a write-then-read race), and rendering both made the duplicate
|
||||||
|
// detector mark BOTH entries as the "recommended" keeper.
|
||||||
|
const _seen = new Set();
|
||||||
|
skills = (data.skills || []).filter(sk => {
|
||||||
|
const k = String(sk?.name || sk?.id || '').toLowerCase();
|
||||||
|
if (!k) return true;
|
||||||
|
if (_seen.has(k)) return false;
|
||||||
|
_seen.add(k);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
_loadSkillApprovalThreshold();
|
_loadSkillApprovalThreshold();
|
||||||
// Built-in capabilities are no longer surfaced in the Skills menu.
|
// Built-in capabilities are no longer surfaced in the Skills menu.
|
||||||
loaded = true;
|
loaded = true;
|
||||||
@@ -392,21 +403,11 @@ function _openSkillMenu(btn, card, sk, name, isPublished) {
|
|||||||
};
|
};
|
||||||
if (isPublished) mk(_ICON.unpublish, 'Unpublish', {}, () => _setSkillStatus(name, 'draft'));
|
if (isPublished) mk(_ICON.unpublish, 'Unpublish', {}, () => _setSkillStatus(name, 'draft'));
|
||||||
else mk(_ICON.approve, 'Publish', {}, () => _setSkillStatus(name, 'published'));
|
else mk(_ICON.approve, 'Publish', {}, () => _setSkillStatus(name, 'published'));
|
||||||
mk(_ICON.edit, 'Edit', {}, async () => {
|
// Select — moved up to 2nd so it sits next to Publish/Unpublish
|
||||||
if (!card.classList.contains('doclib-card-expanded')) await _expandSkillCard(card, name);
|
// (bulk actions cluster at the top of the menu).
|
||||||
_toggleSkillEdit(card, name);
|
|
||||||
});
|
|
||||||
mk(_ICON.test, 'Test', {}, () => _testSkill(card, name));
|
|
||||||
// Audit kicks off the bulk audit-all loop (test → judge → fix → retry → demote).
|
|
||||||
// Starts at the top of the list and walks down.
|
|
||||||
mk(_ICON.test, 'Audit', {}, () => _auditAllSkills());
|
|
||||||
mk(_ICON.del, 'Delete', { danger: true }, () => _deleteSkill(name, card));
|
|
||||||
|
|
||||||
// Select — enters bulk-select mode and pre-selects this skill. Same pattern
|
|
||||||
// as the email/documents/brain Select item, with the email bullet icon.
|
|
||||||
const selItem = document.createElement('button');
|
const selItem = document.createElement('button');
|
||||||
selItem.className = 'skill-kebab-item';
|
selItem.className = 'skill-kebab-item';
|
||||||
selItem.innerHTML = '<span style="display:inline-flex;width:14px;height:14px;align-items:center;justify-content:center;"><span style="font-size:16px;line-height:1;">●</span></span><span>Select</span>';
|
selItem.innerHTML = '<svg class="memory-select-btn-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="3" fill="currentColor" stroke="none"/></svg><span>Select</span>';
|
||||||
selItem.addEventListener('click', (e) => {
|
selItem.addEventListener('click', (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
menu.remove();
|
menu.remove();
|
||||||
@@ -416,6 +417,15 @@ function _openSkillMenu(btn, card, sk, name, isPublished) {
|
|||||||
});
|
});
|
||||||
menu.appendChild(selItem);
|
menu.appendChild(selItem);
|
||||||
|
|
||||||
|
mk(_ICON.edit, 'Edit', {}, async () => {
|
||||||
|
if (!card.classList.contains('doclib-card-expanded')) await _expandSkillCard(card, name);
|
||||||
|
_toggleSkillEdit(card, name);
|
||||||
|
});
|
||||||
|
mk(_ICON.test, 'Test', {}, () => _testSkill(card, name));
|
||||||
|
// Audit kicks off the bulk audit-all loop (test → judge → fix → retry → demote).
|
||||||
|
mk(_ICON.test, 'Audit', {}, () => _auditAllSkills());
|
||||||
|
mk(_ICON.del, 'Delete', { danger: true }, () => _deleteSkill(name, card));
|
||||||
|
|
||||||
// Mobile-only Cancel — mirrors the email/documents/brain popup pattern.
|
// Mobile-only Cancel — mirrors the email/documents/brain popup pattern.
|
||||||
// CSS hides `.dropdown-cancel-mobile` on desktop where outside-click
|
// CSS hides `.dropdown-cancel-mobile` on desktop where outside-click
|
||||||
// already dismisses cleanly.
|
// already dismisses cleanly.
|
||||||
|
|||||||
Reference in New Issue
Block a user