From 189c1a3d24b859d3ebf351cff24bbec788a0251a Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Thu, 24 Sep 2026 08:33:46 -0700 Subject: Preserve restored thread state across rapid refreshes --- src/app.js | 4 ++-- src/features/messages.js | 18 +++++++++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/app.js b/src/app.js index 5087def..e00e7e0 100644 --- a/src/app.js +++ b/src/app.js @@ -607,7 +607,7 @@ function renderInner() { // Get started here is what flashed the sign-in page over a // wallet that was about to restore. Every other screen is a // deliberate choice and still paints; this one waits. - ? h('div', { class: 'col', style: 'gap:16px' }, brandHeader(false)) + ? h('div', { class: 'col', style: 'gap:16px' }, BOOT_NAV ? null : brandHeader(false)) : shouldOnboard() ? onboardScreen() : unlockScreen()); // Navigation animates; background repaints must not. The key is every // ui field that decides which page is on screen. @@ -5579,7 +5579,7 @@ const INTENT_URI = (() => { } catch { return null; } })(); const FEATURES = buildFeatures(ctx); -// Deferred features (gifts, NWC, hats) hold placeholder slots in FEATURES — +// Deferred features (gifts, NWC, POS) hold placeholder slots in FEATURES — // hook precedence is position — and arrive as separate chunks right after // boot, filled in place. If a wallet opened before they landed, each newcomer // gets the init() it missed; if the wallet locked again, it doesn't. diff --git a/src/features/messages.js b/src/features/messages.js index 9cc0175..a73809e 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -1811,7 +1811,13 @@ export function messagesFeature(ctx) { const ref = m ? parseNostrRef(m[1].toLowerCase()) : null; return ref && ref.type === 'event' ? ref : null; })(); - if (urlNote) { + // The app's history already holds this exact thread on a reload. Leave its + // state and URL intact so wallet restore can paint the header and thread + // together; the public deep-link path is only for a NEW visit. + const restoringUrlThread = (() => { + try { return !!urlNote && history.state?.nav?.noteThread?.focusId === urlNote.id; } catch { return false; } + })(); + if (urlNote && !restoringUrlThread) { try { history.replaceState(null, '', '/'); } catch {} ui.pubProf = true; // a reload of a thread the history restores by itself needs no fetch @@ -5731,6 +5737,16 @@ export function messagesFeature(ctx) { const noteSep = () => h('div', { style: 'height:1px;background:var(--border,rgba(128,128,128,.18));margin:0 -14px' }); function renderThreadStable() { if (!ui.noteThread || ui.noteThread.scrollPending || typeof document === 'undefined') { render(); return; } + // At the very top the HEADER is the anchor. Pinning the first post instead + // turns a late header/control row into an unwanted downward scroll. + if (window.scrollY <= 1) { + render(); + if (window.scrollY) window.scrollTo(0, 0); + requestAnimationFrame(() => { + if (ui.noteThread && window.scrollY <= 80) window.scrollTo(0, 0); + }); + return; + } const rows = [...document.querySelectorAll('.thread-page [data-zap-post]')]; const anchor = rows.find((el) => el.getAttribute('data-focus-note') === '1' && el.getBoundingClientRect().top >= 0 && el.getBoundingClientRect().top < innerHeight) -- cgit v1.2.3