From d74b1ad667abea3b82bdb4bbddb9f1d0047903d4 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Mon, 28 Sep 2026 22:05:29 -0700 Subject: Send suggestions keep their rows across renders, so hovering one no longer flickers. The panel was swapped for fresh nodes on every background render, which dropped :hover each time. Now it's one node for the page's life and its rows are rebuilt only when the results change. Co-Authored-By: Claude Opus 5.5 --- src/app.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/app.js b/src/app.js index 5e3fba4..febb2cd 100644 --- a/src/app.js +++ b/src/app.js @@ -4832,7 +4832,7 @@ function destReady(a) { // Recipient search under the destination input: usernames and npubs resolve // to candidates with avatars; picking one runs the same path as pasting. -const sendSearch = { rows: null, sync: null }; +const sendSearch = { rows: null, sync: null, pick: null, el: null }; let sendRevealTimer = null; // With the phone keyboard up, the space under the recipient field is scarce — // park the field at the top of the view so the candidate list gets what's @@ -4882,12 +4882,19 @@ function recipientRow(s, r, i) { }; // The suggestions panel gets the same imperative treatment — see the // sendSearcher note: a render between key repeats kills backspace-hold. - const suggest = i === 0 ? h('div', { class: 'list send-suggest', style: 'display:none', 'data-fresh': '1' }) : null; + // One panel for the life of the page, rows rebuilt only when the results + // change: a background render must not swap the row under the pointer + // (a fresh node drops :hover, and the highlight blinked on every tick). + // data-fresh still installs this exact node wherever the morph lands it. + const suggest = i === 0 ? (sendSearch.el ||= h('div', { class: 'list send-suggest', style: 'display:none', 'data-fresh': '1' })) : null; const syncSuggest = () => { if (!suggest) return; - const show = sendSearch.rows && sendSearch.rows.length && searchable(r.address); - suggest.replaceChildren(...(show ? resultRows(h, sendSearch.rows, pickRecipient, (pk, node) => featureHook('wrapAvatar', pk, node)) : [])); - suggest.style.display = show ? '' : 'none'; + const show = !!(sendSearch.rows && sendSearch.rows.length && searchable(r.address)); + const rows = show ? sendSearch.rows : null; + if (suggest._rows === rows) return; + suggest._rows = rows; + suggest.replaceChildren(...(rows ? resultRows(h, rows, (cand) => sendSearch.pick(cand), (pk, node) => featureHook('wrapAvatar', pk, node)) : [])); + suggest.style.display = rows ? '' : 'none'; }; if (i === 0) sendSearch.sync = syncSuggest; @@ -4982,6 +4989,7 @@ function recipientRow(s, r, i) { ) : null ); syncCheck(); + if (i === 0) sendSearch.pick = pickRecipient; // the persistent rows call this render's syncSuggest(); return row; } -- cgit v1.2.3