feat: pre-load reply threads from the feed

When a reply is visible in NoteCompose, eagerly subscribe to its thread
root (filter on the root's e/a tag, like the thread screen) so opening the
conversation finds it already loaded. The observer resolves the root and
keys on its id, so sibling replies share one subscription and root notes
are skipped.
This commit is contained in:
Claude
2026-06-16 00:26:13 +00:00
parent 72c0558bb9
commit c7f5ab83b0
2 changed files with 34 additions and 0 deletions
@@ -48,6 +48,7 @@ import com.vitorpamplona.amethyst.ui.note.ReplyNoteComposition
import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags
import com.vitorpamplona.amethyst.ui.note.nip22Comments.DisplayExternalId
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.datasources.PreloadThreadForReply
import com.vitorpamplona.amethyst.ui.theme.HalfVertSpacer
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
import com.vitorpamplona.amethyst.ui.theme.placeholderText
@@ -82,6 +83,10 @@ fun RenderTextEvent(
val noteEvent = note.event ?: return
if (unPackReply != ReplyRenderType.NONE) {
// Eagerly pull the rest of this reply's thread while it's on screen, so opening
// the conversation finds it already loaded. No-op when the note is a root itself.
PreloadThreadForReply(note, accountViewModel)
val canShowReply by
remember(note) {
derivedStateOf {
@@ -22,8 +22,11 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.datasources
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import com.vitorpamplona.amethyst.commons.model.ThreadAssembler
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.LifecycleAwareKeyDataSourceSubscription
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.quartz.nip01Core.core.HexKey
@@ -52,3 +55,29 @@ fun ThreadFilterAssemblerSubscription(
LifecycleAwareKeyDataSourceSubscription(state, filterAssembler)
}
/**
* Eagerly pre-loads the whole thread of a reply that is visible in a feed.
*
* When a reply shows up in `NoteCompose`, this resolves the thread root and opens
* the same root subscription the thread screen uses (a filter on the root's `e`/`a`
* tag, covering NIP-10 and NIP-22 event/addressable roots), so tapping into the
* conversation finds it already loaded. Keying on the resolved root id means every
* visible reply that shares a root collapses onto a single subscription, and a note
* that is itself a root (no parent) is skipped — nothing to pre-load.
*/
@Composable
fun PreloadThreadForReply(
note: Note,
accountViewModel: AccountViewModel,
) {
val rootId =
remember(note) {
val root = ThreadAssembler(LocalCache).findRoot(note.idHex)
if (root != null && root != note) root.idHex else null
}
if (rootId != null) {
ThreadFilterAssemblerSubscription(rootId, accountViewModel)
}
}