diff --git a/lib/public/modules/emoji-picker.js b/lib/public/modules/emoji-picker.js index 799543ef..9295108d 100644 --- a/lib/public/modules/emoji-picker.js +++ b/lib/public/modules/emoji-picker.js @@ -249,12 +249,24 @@ export function buildEmojiPicker(opts) { } function doUpload() { - if (!pendingFile) return; + // Both early returns below used to be silent — the control (rendered + // by showSlugConfirm) is only reachable once a file is already + // selected, but a defensive check that fires with zero user feedback + // is indistinguishable from the picker doing nothing at all. Surface + // both via the same toast used for server-side upload failures (line + // ~285) so "nothing happened" never happens silently again. + if (!pendingFile) { + showToast("Couldn't add custom emoji", "warn"); + return; + } var slug = slugInput.value.trim().toLowerCase() .replace(/[^a-z0-9_-]+/g, "-") .replace(/^-+|-+$/g, "") .slice(0, 64); - if (!slug || !/^[a-z0-9_-]{1,64}$/.test(slug)) return; + if (!slug || !/^[a-z0-9_-]{1,64}$/.test(slug)) { + showToast("Enter a valid name", "warn"); + return; + } // Check for duplicate in the already-fetched list var existing = customList || []; @@ -274,9 +286,14 @@ export function buildEmojiPicker(opts) { showToast(result.error || "Upload failed", "warn"); return; } - // Refresh the custom grid after upload + // Refresh the custom grid after upload. buildCustomPanel() already + // repaints the grid it just (re)built via its own fresh + // renderCustomGrid closure (see below) — calling the outer + // renderCustomGrid here re-renders the ORPHANED grid captured by + // this closure at the time doUpload's panel was built, which is + // detached from the DOM the moment buildCustomPanel() runs again. customList = null; - fetchCustomList(function () { buildCustomPanel(); renderCustomGrid(); }); + fetchCustomList(function () { buildCustomPanel(); }); }); } @@ -352,8 +369,12 @@ export function buildEmojiPicker(opts) { delBtn.addEventListener("click", function (e) { e.stopPropagation(); deleteCustomIcon(entry.slug).then(function () { + // Same stale-closure hazard as the upload success path above: + // buildCustomPanel() already repaints via its own fresh + // renderCustomGrid; the closure captured here is orphaned + // once buildCustomPanel() rebuilds the panel. customList = null; - fetchCustomList(function () { buildCustomPanel(); renderCustomGrid(); }); + fetchCustomList(function () { buildCustomPanel(); }); }); }); tile.appendChild(delBtn);