diff options
| author | Adam Soltys <[email protected]> | 2026-09-15 00:26:31 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-09-15 00:26:31 -0700 |
| commit | feadbb008de9b2fe2332a7f0e190955ae545f07d (patch) | |
| tree | 77133a125cad2903782fa747d5cc8fa834a65052 /src | |
| parent | e408672de5b6aa450995271a36b7483ca94db049 (diff) | |
| download | coinosv3-feadbb008de9b2fe2332a7f0e190955ae545f07d.tar.gz coinosv3-feadbb008de9b2fe2332a7f0e190955ae545f07d.zip | |
Opening a reply scrolls the thread to that reply.
A thread opened from a reply highlighted the reply but never scrolled
to it, so in a long thread it sat below the fold and the page looked
like it had missed. The thread now scrolls the focused note into view
after its paint — and if the replies are still loading, after the
render that brings them. Opening the root itself means the top.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Diffstat (limited to 'src')
| -rw-r--r-- | src/features/messages.js | 19 |
1 files changed, 18 insertions, 1 deletions
diff --git a/src/features/messages.js b/src/features/messages.js index fd95b62..d3caafe 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -4133,6 +4133,7 @@ export function messagesFeature(ctx) { if (!pending) watchZaps([ev.id]); return h('div', { class: 'row', + 'data-focus-note': focus ? '1' : undefined, style: 'gap:10px;align-items:flex-start;padding:10px 0' + (openable ? ';cursor:pointer' : '') + (pending ? ';opacity:.55' : '') @@ -4214,7 +4215,7 @@ export function messagesFeature(ctx) { return c; } function openNoteThread(ev) { - ui.noteThread = { rootId: rootIdOf(ev), focusId: ev.id, seed: ev }; + ui.noteThread = { rootId: rootIdOf(ev), focusId: ev.id, seed: ev, scrollPending: true }; ui.profOverThread = false; // a freshly opened thread goes on top render(); } @@ -4385,6 +4386,22 @@ export function messagesFeature(ctx) { if (c.status === 'loading') kids.push(h('div', { class: 'row', style: 'justify-content:center;padding:12px' }, h('span', { class: 'spinner sm' }))); else if (!c.replies.length) kids.push(h('div', { class: 'small faint', style: 'text-align:center;padding:10px 0' }, t('threadNoReplies'))); if (!boxPlaced) kids.push(replyBox()); + // A tapped reply deep in a long thread has to be ON SCREEN when the + // thread opens — highlighting it somewhere below the fold reads as the + // thread having missed it. Scroll to it after this paint; if the replies + // are still loading the row isn't there yet, so the request stays armed + // for the render that brings it. The root itself means the top. + if (s.scrollPending) { + const isRoot = !!c.root && s.focusId === c.root.id; + setTimeout(() => { + if (ui.noteThread !== s || !s.scrollPending) return; + if (isRoot) { s.scrollPending = false; try { window.scrollTo({ top: 0 }); } catch {} return; } + const el = document.querySelector('[data-focus-note]'); + if (!el) return; + s.scrollPending = false; + try { el.scrollIntoView({ block: 'center' }); } catch {} + }, 60); + } return h('div', { class: 'col', style: 'gap:16px' }, // full header: search/chat/settings stay reachable mid-thread (only // the public no-wallet surface drops the action row) |
