* fix: approval pending check broken by stale has_pending import (#228) api/routes.py imported has_pending/pop_pending from tools.approval, but the agent module renamed has_pending to has_blocking_approval (checks gateway queue, not _pending dict) and removed pop_pending. The import fell through to fallback lambdas that always returned False, making GET /api/approval/pending always return {pending:null} even after a successful inject_test. Fix: check _pending directly under _lock — same dict submit_pending writes to. Stale imports removed. Before: 554 pass, 1 fail | After: 555 pass, 0 fail * fix: move login JS into external file, remove inline handlers (#226) Login page used inline onsubmit/onkeydown handlers and an inline <script> block — all blocked by strict script-src CSP, causing silent login failure. Fix: extract doLogin() and Enter key listener into static/login.js (served from /static/, already a public path). Form uses id='login-form' and data-* attributes for i18n strings instead of injected JS literals. Also guards res.json() parse with try/catch so non-JSON error bodies (e.g. HTTP 500) show the password-error fallback instead of 'Connection failed'. Fixes #222. * fix: improve update error messages when pull fails (#227) _apply_update_inner() ran git pull --ff-only and returned only raw stderr on failure, making all failure modes indistinguishable. Fix: explicit git fetch before pull; if fetch fails, returns human-readable network error. Diverged history and missing upstream tracking branch each get distinct messages with exact recovery commands. Generic fallback truncates to 300 chars and shows sentinel when git produces no output. Also adds tests/test_update_checker.py with 13 tests covering all 4 new diagnostic code paths (0 tests existed before). Fixes #223. * fix: stabilize 30s terminal approval prompt visibility (#225) Adds minimum 30-second visibility guard for the approval card using _approvalVisibleSince, _approvalHideTimer, and a signature fingerprint to deduplicate repeated poll ticks. Fix: respondApproval() and all stream-end paths (done/cancel/apperror/ error/start-error) now call hideApprovalCard(true) so the card hides immediately when the user responds or the session ends. The 30s guard only applies to mid-session poll ticks where the approval is still live but briefly absent. Adds 11 structural tests covering the new timer variables, force parameter, force-on-respond, force-on-stream-end, and poll-loop no-force behavior. * feat: replace emoji icons with self-hosted Lucide SVG icons (#221) Replaces all sidebar/button emoji icons with SVG paths from Lucide bundled in static/icons.js (no CDN dependency). Adds li(name) function returning inline SVG geometry from a hardcoded whitelist — unknown keys return '' so dynamic server-supplied names never inject arbitrary SVG. Changes: - static/icons.js: new file with 21 icon paths + li() renderer - static/index.html: all nav/action buttons now use li() icons - static/ui.js: toolIcon(), fileIcon() use li() for tool/file icons - static/messages.js: cancelStream button uses SVG square stop icon - .gitignore: adds node_modules/ entry Verified: all 35 onclick= functions exist in JS, all 21 li() calls reference defined icons, applyBotName() selectors intact, version label present, no removed IDs referenced by JS. * docs: v0.44.0 release notes, bump version, update test counts --------- Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
70 lines
7.9 KiB
JavaScript
70 lines
7.9 KiB
JavaScript
// ── Lucide icon library (self-hosted SVG paths, no CDN dependency) ──────────
|
||
// All icons are 24×24 viewBox, stroke-based, currentColor.
|
||
// Usage: li('folder') → returns a ready-to-embed SVG string
|
||
// The returned SVG uses display:inline-block + vertical-align so it sits
|
||
// neatly beside text in both HTML templates and innerHTML assignments.
|
||
|
||
const LI_PATHS = {
|
||
// Navigation tabs
|
||
'message-square': '<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>',
|
||
'calendar': '<rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/>',
|
||
'layers': '<path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/>',
|
||
'lightbulb': '<path d="M12 2a7 7 0 0 1 7 7c0 2.5-1.3 4.7-3.2 6H8.2C6.3 13.7 5 11.5 5 9a7 7 0 0 1 7-7z"/><line x1="9" y1="17" x2="15" y2="17"/><line x1="10" y1="20" x2="14" y2="20"/>',
|
||
'folder': '<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>',
|
||
'list-todo': '<rect x="3" y="5" width="6" height="6" rx="1"/><path d="m3 17 2 2 4-4"/><path d="M13 6h8"/><path d="M13 12h8"/><path d="M13 18h8"/>',
|
||
// Editing / actions
|
||
'pencil': '<path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/>',
|
||
'chevron-down': '<polyline points="6 9 12 15 18 9"/>',
|
||
'download': '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>',
|
||
'upload': '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/>',
|
||
'braces': '<path d="M8 3H7a2 2 0 0 0-2 2v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5c0 1.1.9 2 2 2h1"/><path d="M16 3h1a2 2 0 0 1 2 2v5a2 2 0 0 0 2 2 2 2 0 0 0-2 2v5a2 2 0 0 1-2 2h-1"/>',
|
||
'trash-2': '<path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/>',
|
||
'settings': '<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>',
|
||
'alert-triangle': '<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/>',
|
||
'refresh-cw': '<polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>',
|
||
'check': '<polyline points="20 6 9 17 4 12"/>',
|
||
'lock': '<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>',
|
||
'star': '<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>',
|
||
'x': '<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>',
|
||
'square': '<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>',
|
||
'plus': '<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>',
|
||
'arrow-up': '<line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/>',
|
||
'loader': '<line x1="12" y1="2" x2="12" y2="6"/><line x1="12" y1="18" x2="12" y2="22"/><line x1="4.93" y1="4.93" x2="7.76" y2="7.76"/><line x1="16.24" y1="16.24" x2="19.07" y2="19.07"/><line x1="2" y1="12" x2="6" y2="12"/><line x1="18" y1="12" x2="22" y2="12"/><line x1="4.93" y1="19.07" x2="7.76" y2="16.24"/><line x1="16.24" y1="7.76" x2="19.07" y2="4.93"/>',
|
||
// Tool icons
|
||
'terminal': '<polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/>',
|
||
'file-text': '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/>',
|
||
'file-pen': '<path d="M12 22h6a2 2 0 0 0 2-2V7l-5-5H6a2 2 0 0 0-2 2v10"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10.4 19.4 14 16l-4-1 .4 4.4z"/><path d="m14 16 1.5-1.5a2.12 2.12 0 0 1 3 3L17 19"/>',
|
||
'search': '<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>',
|
||
'globe': '<circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>',
|
||
'play': '<polygon points="5 3 19 12 5 21 5 3"/>',
|
||
'wrench': '<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>',
|
||
'brain': '<path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96-.44 2.5 2.5 0 0 1-2.96-3.08 3 3 0 0 1-.34-5.58 2.5 2.5 0 0 1 1.32-4.24 2.5 2.5 0 0 1 1.98-3A2.5 2.5 0 0 1 9.5 2z"/><path d="M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96-.44 2.5 2.5 0 0 0 2.96-3.08 3 3 0 0 0 .34-5.58 2.5 2.5 0 0 0-1.32-4.24 2.5 2.5 0 0 0-1.98-3A2.5 2.5 0 0 0 14.5 2z"/>',
|
||
'book-open': '<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>',
|
||
'clock': '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>',
|
||
'bot': '<rect x="3" y="11" width="18" height="10" rx="2"/><circle cx="12" cy="5" r="2"/><path d="M12 7v4"/><line x1="8" y1="16" x2="8" y2="16"/><line x1="16" y1="16" x2="16" y2="16"/>',
|
||
'eye': '<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/>',
|
||
'shuffle': '<polyline points="16 3 21 3 21 8"/><line x1="4" y1="20" x2="21" y2="3"/><polyline points="21 16 21 21 16 21"/><line x1="15" y1="15" x2="21" y2="21"/><line x1="4" y1="4" x2="9" y2="9"/>',
|
||
// File-type icons
|
||
'image': '<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/>',
|
||
'file-code': '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><polyline points="10 13 8 15 10 17"/><polyline points="14 13 16 15 14 17"/>',
|
||
'zap': '<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>',
|
||
// Suggestion buttons
|
||
'clipboard-list': '<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><rect x="8" y="2" width="8" height="4" rx="1" ry="1"/><line x1="9" y1="12" x2="15" y2="12"/><line x1="9" y1="16" x2="12" y2="16"/>',
|
||
'map': '<polygon points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6"/><line x1="8" y1="2" x2="8" y2="18"/><line x1="16" y1="6" x2="16" y2="22"/>',
|
||
};
|
||
|
||
/**
|
||
* Returns a Lucide SVG string for the given icon name.
|
||
* @param {string} name – key in LI_PATHS (e.g. 'folder', 'trash-2')
|
||
* @param {number} size – width/height in px (default 16)
|
||
* @returns {string} SVG element string ready for innerHTML
|
||
*/
|
||
function li(name, size = 16) {
|
||
const p = LI_PATHS[name];
|
||
if (!p) { console.warn('li(): unknown icon', name); return ''; }
|
||
return `<svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" `
|
||
+ `stroke="currentColor" stroke-width="2" stroke-linecap="round" `
|
||
+ `stroke-linejoin="round" aria-hidden="true" `
|
||
+ `style="display:inline-block;vertical-align:-0.15em;flex-shrink:0">${p}</svg>`;
|
||
}
|