aboutsummaryrefslogtreecommitdiffstats
path: root/src/features
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-10-09 20:48:38 -0700
committerAdam Soltys <[email protected]>2026-10-09 20:48:38 -0700
commit557831a04d71a155699124395f55d394ad5b2f4f (patch)
tree41d1fb0523692d2db1914239ef48da94cc972c2f /src/features
parent095caadcba5a63f5969e50777d7e482c1d32174e (diff)
downloadcoinosv3-557831a04d71a155699124395f55d394ad5b2f4f.tar.gz
coinosv3-557831a04d71a155699124395f55d394ad5b2f4f.zip
Messages: a tap on a conversation's face opens THAT person. The avatar's tap handlers were attached with addEventListener, holding the person it was first drawn for; the morph transplants only h()'s on* properties, so when a new message re-sorted the list and the rows were patched in place, a face kept its old handler — a tap on Bill opened someone else's profile, and once that page had opened, a fresh render hid the evidence. The avatar now sets onclick/onpointerdown claimed in _evs (transplanted like any h() handler), and conversation rows are keyed by person so a re-sort moves them. tools/dm-row-resort-test.js (on the old code, Bill's face opened Alice).
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
Diffstat (limited to 'src/features')
-rw-r--r--src/features/messages.js15
1 files changed, 13 insertions, 2 deletions
diff --git a/src/features/messages.js b/src/features/messages.js
index 90b080b..b3066c0 100644
--- a/src/features/messages.js
+++ b/src/features/messages.js
@@ -3699,8 +3699,15 @@ export function messagesFeature(ctx) {
// and unlike prefetching every row on paint (which cost sixty requests
// for twenty rows and starved the profile batch) this only ever asks
// about the person actually being tapped.
- node.addEventListener('pointerdown', () => { try { prefetchProfilePage(pk); } catch {} }, { passive: true });
- node.addEventListener('click', (e) => { e.stopPropagation(); openProfile(pk); });
+ // As on* properties claimed in _evs, the way h() attaches handlers, so
+ // the morph transplants them: a list that re-sorts (a new message
+ // lifting a conversation to the top) patches this node into a
+ // DIFFERENT person's avatar, and an addEventListener'd handler stayed
+ // behind holding the old person — a tap on Bill's row opened someone
+ // else's profile, until the next fresh render.
+ node.onpointerdown = () => { try { prefetchProfilePage(pk); } catch {} };
+ node.onclick = (e) => { e.stopPropagation(); openProfile(pk); };
+ (node._evs || (node._evs = new Set())).add('onpointerdown').add('onclick');
}
return hook('wrapAvatar', pk, node) || hatStamp(pk, node) || node;
};
@@ -12407,6 +12414,9 @@ export function messagesFeature(ctx) {
? h('div', { class: 'list' }, shownDms.map((row) => row.g ? wnGroupRow(row.g) : (({ peer, last, unread }) =>
h('div', {
class: 'item chat-thread-row' + (unread ? ' unread' : ''),
+ // keyed: a new message re-sorts the list, and the row moves
+ // with its person instead of being repainted as someone else
+ 'data-key': 'dm:' + peer,
onClick: () => { ui.msgView = 'dm'; ui.msgPeer = peer; ui.msgStick = true; render(); },
},
avatar(peer),
@@ -12838,6 +12848,7 @@ export function messagesFeature(ctx) {
for (const pk of wn.accounts(g).slice(0, 12)) profileOf(pk);
return h('div', {
class: 'item chat-thread-row' + (unread ? ' unread' : ''),
+ 'data-key': 'grp:' + g.id,
onClick: () => { ui.msgView = 'grp'; ui.msgPeer = g.id; ui.msgStick = true; ui.msgMembers = false; render(); },
},
h('div', { class: 'chat-avatar fallback' }, name.slice(0, 2)),