From 65548ebf36315680625c6cbd9e9967c09f9d7b06 Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Thu, 26 Mar 2026 23:06:01 -0400 Subject: [PATCH] feat: Lucide SVG icons throughout main UI Replace all emoji/unicode icons with Lucide SVG icons: - Mode select dropdown: message-circle / pencil / lock / bot - Send button: arrow-up (chat/OTR), pencil (note), zap (agent) - Stop button: square icon - Header nav already had Lucide SVGs; render_icons() now called at init Add icon_html() + render_icons() helpers; update update_mode_ui() and open_mode_dropdown() to use innerHTML + lucide.createIcons(). CSS: .btn-icon alignment, inline-flex on .hdr-btn / .hdr-dd-item / #send / #stop. Co-Authored-By: Claude Sonnet 4.6 --- cortex/static/app.js | 44 +++++++++++++++++++++++++++++----------- cortex/static/index.html | 32 +++++++++++++++++++++-------- cortex/static/style.css | 23 +++++++++++++++++---- 3 files changed, 75 insertions(+), 24 deletions(-) diff --git a/cortex/static/app.js b/cortex/static/app.js index 3d3af78..99004b7 100644 --- a/cortex/static/app.js +++ b/cortex/static/app.js @@ -17,6 +17,12 @@ const settings_btn_el = document.getElementById('settings-btn'); const settings_dd_el = document.getElementById('settings-dropdown'); + // ── Lucide icon helpers ─────────────────────────────────────── + function icon_html(name, size = 16) { + return ``; + } + function render_icons() { if (window.lucide) lucide.createIcons(); } + // User/persona injected by the server at /{user}/{persona} const CORTEX_USER = (window.CORTEX_CONFIG || {}).user || 'scott'; const CORTEX_PERSONA = (window.CORTEX_CONFIG || {}).persona || 'inara'; @@ -69,12 +75,17 @@ // ── Input mode — dropdown select with MRU ordering ────────── const MODES = { - chat: { icon: '💬', label: 'Chat' }, - note: { icon: '📝', label: 'Note' }, - otr: { icon: '🔒', label: 'OTR' }, - agent: { icon: '🥸', label: 'Agent' }, + chat: { icon: 'message-circle', label: 'Chat' }, + note: { icon: 'pencil', label: 'Note' }, + otr: { icon: 'lock', label: 'OTR' }, + agent: { icon: 'bot', label: 'Agent' }, + }; + const send_defs = { + chat: { icon: 'arrow-up', label: 'Send' }, + note: { icon: 'pencil', label: 'Note' }, + otr: { icon: 'arrow-up', label: 'Send' }, + agent: { icon: 'zap', label: 'Run' }, }; - const send_labels = { chat: '↑ Send', note: '📝 Note', otr: '↑ Send', agent: '⚡ Run' }; let current_mode = localStorage.getItem('current_mode') || 'chat'; let note_public = false; @@ -105,12 +116,13 @@ const btn = document.createElement('button'); btn.className = 'mode-option' + (mode === current_mode ? ' current' : ''); btn.innerHTML = - `${m.icon}${m.label}` + `${icon_html(m.icon, 15)}${m.label}` + (mode === current_mode ? '' : ''); btn.addEventListener('click', () => set_mode(mode)); mode_dropdown_el.appendChild(btn); }); mode_dropdown_el.classList.add('open'); + render_icons(); } function close_mode_dropdown() { @@ -130,10 +142,11 @@ }); function update_mode_ui() { - const m = MODES[current_mode]; + const m = MODES[current_mode]; + const sd = send_defs[current_mode] || send_defs.chat; // Update trigger button - mode_icon_el.textContent = m.icon; + mode_icon_el.innerHTML = icon_html(m.icon, 15); mode_label_el.textContent = m.label; mode_select_btn_el.className = current_mode === 'chat' ? '' : `mode-${current_mode}`; @@ -150,9 +163,10 @@ inputEl.classList.toggle('mode-otr', current_mode === 'otr'); inputEl.classList.toggle('mode-agent', current_mode === 'agent'); - // Send button label - sendBtn.textContent = send_labels[current_mode] || 'Send'; + // Send button label + icon + sendBtn.innerHTML = icon_html(sd.icon) + ' ' + sd.label; + render_icons(); updateInputPlaceholder(); } @@ -716,7 +730,7 @@ inputEl.value = ''; syncHeight(); sendBtn.style.display = 'none'; - stopBtn.style.display = 'block'; + stopBtn.style.display = 'flex'; headerEmoji.classList.add('processing'); activeController = new AbortController(); @@ -808,7 +822,7 @@ inputEl.value = ''; syncHeight(); sendBtn.style.display = 'none'; - stopBtn.style.display = 'block'; + stopBtn.style.display = 'flex'; headerEmoji.classList.add('processing'); activeController = new AbortController(); @@ -1286,3 +1300,9 @@ checkAuthStatus(); // Re-check every 30 minutes setInterval(checkAuthStatus, 30 * 60 * 1000); + + // ── Initial render ──────────────────────────────────────────── + // Process all static Lucide SVGs in the header + stop button, + // and seed the mode UI (which also calls render_icons internally). + update_mode_ui(); + render_icons(); diff --git a/cortex/static/index.html b/cortex/static/index.html index 19f1ea7..85c2732 100644 --- a/cortex/static/index.html +++ b/cortex/static/index.html @@ -21,6 +21,7 @@ +
@@ -32,20 +33,35 @@
@@ -149,7 +165,7 @@
- +
diff --git a/cortex/static/style.css b/cortex/static/style.css index 44f5f22..e0cb91c 100644 --- a/cortex/static/style.css +++ b/cortex/static/style.css @@ -183,7 +183,13 @@ .persona-dropdown .pd-add:hover { color: var(--text); } + /* Lucide SVG icon alignment */ + .btn-icon { display: inline-block; vertical-align: middle; flex-shrink: 0; pointer-events: none; } + .hdr-btn { + display: inline-flex; + align-items: center; + gap: 5px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; @@ -224,7 +230,9 @@ .hdr-dropdown.open { display: block; } .hdr-dd-item { - display: block; + display: flex; + align-items: center; + gap: 8px; width: 100%; text-align: left; padding: 0.55rem 0.85rem; @@ -538,7 +546,7 @@ #mode-select-btn.mode-otr { border-color: rgba(120,80,160,0.6); color: #a87fd4; } #mode-select-btn.mode-agent { border-color: rgba(80,140,200,0.6); color: #7cb9e8; } - #mode-icon { font-size: 1rem; line-height: 1; } + #mode-icon { display: flex; align-items: center; } .mode-arrow { font-size: 0.55rem; color: var(--muted); margin-left: 2px; opacity: 0.5; } /* Dropdown — opens upward; MRU at bottom = closest to button */ @@ -573,7 +581,7 @@ } .mode-option:hover { background: var(--border); color: var(--text); } .mode-option.current { color: var(--text); font-weight: 500; } - .mode-option .opt-icon { font-size: 1rem; line-height: 1; } + .mode-option .opt-icon { display: flex; align-items: center; } .mode-option .opt-check { margin-left: auto; font-size: 0.7rem; opacity: 0.7; } /* Note visibility sub-button — shown below mode-select when note is active */ @@ -630,6 +638,10 @@ /* Send button */ #send { + display: flex; + align-items: center; + justify-content: center; + gap: 6px; background: var(--user-bg); border: 1px solid var(--user-border); color: var(--text); @@ -649,11 +661,14 @@ /* Stop button */ #stop { display: none; + align-items: center; + justify-content: center; + gap: 6px; background: var(--error-bg); border: 1px solid var(--error-border); color: var(--error-text); border-radius: 8px; - padding: 10px 0; + padding: 10px 14px; cursor: pointer; font-size: 0.9rem; text-align: center;