diff options
| author | Adam Soltys <[email protected]> | 2026-10-08 13:50:44 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-08 13:50:44 -0700 |
| commit | e76d643835c45f6624856850137e71e986315eb0 (patch) | |
| tree | a84e1ffffe4389f4a55c0fa797da6ab24c4e0df9 /src/features/messages.js | |
| parent | 175e513273bfb2a362f9a08e3da5d5460a1d91c0 (diff) | |
| download | coinosv3-e76d643835c45f6624856850137e71e986315eb0.tar.gz coinosv3-e76d643835c45f6624856850137e71e986315eb0.zip | |
Prefetch visible post threads after a bounded reading dwell
Diffstat (limited to 'src/features/messages.js')
| -rw-r--r-- | src/features/messages.js | 33 |
1 files changed, 27 insertions, 6 deletions
diff --git a/src/features/messages.js b/src/features/messages.js index 53f6c99..759b155 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -32,6 +32,7 @@ import { saveInbox } from '../dm-inbox.js'; import { loadMediaIndex, saveMediaIndex, wipeMediaIndex } from '../media-cache.js'; import { mergeFeedWindow } from '../feed-window.js'; import { createThreadStore } from '../thread-cache.js'; +import { createThreadPrefetch } from '../thread-prefetch.js'; import { createFeedCache, FEED_CACHE_POSTS, FEED_SEED_POSTS } from '../feed-cache.js'; import { POPULAR_RELAYS, FIREHOSE_RELAYS, popularCandidates } from '../popular-feed.js'; import { makeSearcher, resultRows, fallbackAvatar, warmSearch, punkImageUrl, punkSmallUrl, primalCache } from '../recipient-search.js'; @@ -8841,10 +8842,12 @@ export function messagesFeature(ctx) { const flagged = !pending && !(ui.revealed && ui.revealed.has(ev.id)) ? reportsOn(ev) : null; if (flagged) return foldedRow(ev, flagged); const openable = open && !pending; + if (openable) threadPrefetch.watch(ev); if (!pending) watchZaps([ev.id]); return h('div', { class: 'row note-post', 'data-zap-post': ev.id, + 'data-thread-prefetch': openable ? ev.id : undefined, 'data-focus-note': focus ? '1' : undefined, style: 'padding:10px 0' + (openable ? ';cursor:pointer' : '') @@ -8888,6 +8891,18 @@ export function messagesFeature(ctx) { // ---- thread view: a note in its conversation ---------------------------- const threadCache = new Map(); // root id -> { status, root, replies } const threadStore = createThreadStore(); + const threadPrefetch = createThreadPrefetch({ + key: rootIdOf, + cached: (ev) => threadCache.has(rootIdOf(ev)), + load: (ev) => threadFor(ev, true).loading, + }); + function trimPrefetchedThreads() { + const idle = [...new Set(threadCache.values())].filter((c) => c.prefetched + && c.status === 'ready' && ui.noteThread?.rootId !== c.rootId); + for (const c of idle.slice(0, Math.max(0, idle.length - 12))) { + for (const [id, entry] of threadCache) if (entry === c) threadCache.delete(id); + } + } const persistThread = (c) => { if (!c.root) return; const faces = {}; @@ -8959,13 +8974,18 @@ export function messagesFeature(ctx) { const marked = es.find((x) => x[3] === 'root'); return (marked || es[0] || [])[1] || ev.id; } - function threadFor(seed) { + function threadFor(seed, prefetch = false) { const requestedRootId = rootIdOf(seed); let c = threadCache.get(requestedRootId) || [...new Set(threadCache.values())] .find((c) => c.replies.some((e) => e.id === seed.id || e.id === requestedRootId)); if (c) { threadCache.set(requestedRootId, c); - c.focusId = seed.id; + if (!prefetch) { + c.focusId = seed.id; + const wasPrefetched = c.prefetched; + c.prefetched = false; + if (wasPrefetched && c.status === 'ready') persistThread(c); + } if (seed.id === c.rootId) c.root ||= seed; else if (!c.replies.some((ev) => ev.id === seed.id)) { c.replies = [...c.replies, seed].sort((a, b) => a.created_at - b.created_at); @@ -8992,14 +9012,14 @@ export function messagesFeature(ctx) { } for (const q of stored?.quotes || []) if (!quoted.get(q.id)?.ev) quoted.set(q.id, { status: 'ready', ev: q, at: Date.now() }); const rootId = stored?.rootId || requestedRootId; - c = { status: stored ? 'ready' : 'loading', rootId, focusId: seed.id, + c = { status: stored ? 'ready' : 'loading', rootId, focusId: seed.id, prefetched: prefetch, root: stored?.root || (seed.id === rootId ? seed : null), replies: stored?.replies || [], counts: stored?.counts || {} }; if (seed.id !== rootId && !c.replies.some((e) => e.id === seed.id)) c.replies.push(seed); c.replies.sort((a, b) => a.created_at - b.created_at); threadCache.set(requestedRootId, c); threadCache.set(rootId, c); if (ui.noteThread?.focusId === seed.id) ui.noteThread.rootId = rootId; - (async () => { + c.loading = (async () => { // Where a thread lives: the reply's author, the people it tags (the // root's author is usually first among them), the relay hints on its // e tags, and ours. The reply author's relays alone missed roots @@ -9052,7 +9072,7 @@ export function messagesFeature(ctx) { .filter((e) => e && e.id !== c.rootId && !seen.has(e.id) && seen.add(e.id)) .sort((a, b) => a.created_at - b.created_at); c.status = 'ready'; - persistThread(c); + if (!c.prefetched) persistThread(c); if (ui.noteThread && ui.noteThread.rootId === c.rootId) { renderThreadStable(); // The inline reply box may have MOVED on this render (it slots under @@ -9068,7 +9088,7 @@ export function messagesFeature(ctx) { })().catch(() => { c.status = 'ready'; if (ui.noteThread && ui.noteThread.rootId === c.rootId) renderThreadStable(); - }); + }).finally(trimPrefetchedThreads); return c; } function openNoteThread(ev) { @@ -13250,6 +13270,7 @@ export function messagesFeature(ctx) { clearTimeout(feedAheadTimer); feedAheadTimer = null; for (const c of feedStates.values()) c.stopped = true; feedStates.clear(); + threadPrefetch.stop(); threadCache.clear(); follows = null; followsAt = 0; feed = null; relayLists = null; mutes = null; mutesAt = 0; |
