From 04bc809c7162565615db797cefd2636c2bdb5dc0 Mon Sep 17 00:00:00 2001 From: r-zakarya <82443831+r-zakarya@users.noreply.github.com> Date: Thu, 23 Jul 2026 21:19:21 +0100 Subject: [PATCH] fix(editor): refresh the icon preview on outside icon writes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A10: the editor's icon preview is painted by a closure nothing outside can reach, so a favicon fetched from Settings (or an import overwrite) updated the cards and left the open editor showing the old initials. soIconField publishes its painter; saveEntryIcon — the choke point for every icon write — routes through editorAbsorbChange like tag/folder already do. Also whitelists .toast and #busyOverlay in the editor's click-outside: both can sit under the cursor during a long operation, and clicking one counted as clicking away. Co-Authored-By: Claude Opus 4.8 --- js/app.favicon.js | 4 ++++ js/app.js | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/js/app.favicon.js b/js/app.favicon.js index 8db0cbd..bfdaf5b 100644 --- a/js/app.favicon.js +++ b/js/app.favicon.js @@ -52,7 +52,11 @@ async function saveEntryIcon(entryId, dataUri) { try { const b = await r.json(); if (b && b.error) msg = b.error; } catch (_) {} toast('Icon NOT saved: ' + msg, 'error'); + return; } + // Choke point for every icon write (favicon backfill, upload, remove, + // import overwrite) — refresh the editor if it's open on this entry. + editorAbsorbChange(entryId, { icon: dataUri || '' }); } catch (e) { toast('Icon save failed: ' + (e && e.message || e), 'error'); } diff --git a/js/app.js b/js/app.js index e65aae6..94351d2 100644 --- a/js/app.js +++ b/js/app.js @@ -3508,6 +3508,7 @@ function editorAbsorbChange(id, change) { const sel = $('#soFolder'); if (sel) sel.value = change.folder; } + if (change.icon !== undefined && soIconRepaint) soIconRepaint(change.icon || null); if (change.tag && !soState.tags.includes(change.tag)) { soState.tags.push(change.tag); soState.original.tags = soState.original.tags @@ -4271,6 +4272,11 @@ function soEditableField(label, id, value) { // came from. const ICON_MAX_BYTES = 256 * 1024; // matches server-side cap (256 KB) +// Repaint hook for the editor's icon preview, published by soIconField so an +// icon written from OUTSIDE the editor (favicon backfill, import) can refresh +// it. Cleared on close — the preview node dies with the panel. +let soIconRepaint = null; + function soIconField(entry) { const wrap = el('div', { class: 'slideover-field so-icon-field' }); const preview = el('div', { class: 'so-icon-preview' }); @@ -4289,6 +4295,7 @@ function soIconField(entry) { } } paintPreview(entry.icon_b64 || null); + soIconRepaint = paintPreview; const actions = el('div', { class: 'so-icon-actions' }); @@ -4953,6 +4960,7 @@ function closeSlideOver() { const ae = document.activeElement; if (ae && $('#slideover').contains(ae) && typeof ae.blur === 'function') ae.blur(); $('#slideover').classList.remove('is-open'); + soIconRepaint = null; state.selectedId = null; renderGrid(); } @@ -7332,6 +7340,10 @@ async function init() { // themselves what happens to the editor. if (e.target.closest('.batch-bar')) return; if (e.target.closest('.content-header')) return; + // A toast or the busy spinner can land under the cursor mid-operation; + // clicking one must not count as "clicked away from the editor". + if (e.target.closest('.toast')) return; + if (e.target.closest('#busyOverlay')) return; if (e.target.closest('.cols-menu')) return; if (e.target.closest('.filters-menu')) return; if (e.target.closest('.user-menu')) return;