From fdc09ad03991435b8546f140ec8202fb1dadbdfa Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 10 Jun 2026 14:30:02 +0000 Subject: [PATCH] perf(clink): cache parsed NOffer for NIP-05 lookups + case-insensitive key The profile-header NIP-05 clink_offer cache stored the raw noffer string, re-running ClinkPointerParser on every cache hit; it now stores the parsed NOffer. The cache key is lowercased since NIP-05 identifiers are case-insensitive, so casing variants no longer trigger duplicate fetches. https://claude.ai/code/session_01NM2TyJtosLdY5ycjyabSRS --- .../profile/header/DrawAdditionalInfo.kt | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DrawAdditionalInfo.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DrawAdditionalInfo.kt index 342f0afe6c..e3ee115acc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DrawAdditionalInfo.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DrawAdditionalInfo.kt @@ -390,14 +390,15 @@ fun getIdentityClaimDescription(identity: IdentityClaimTag): Int = } /** - * Process-wide cache of NIP-05 `.well-known` `clink_offer` lookups, keyed by the nip05 - * address. Without it, every profile visit (and every relay-pushed kind-0 refresh while a - * profile is open) would re-fetch the domain's nostr.json. Caches "no offer" results too so - * profiles without one aren't re-hit. A [ResolvedClinkOffer] wrapper holds the nullable result + * Process-wide cache of NIP-05 `.well-known` `clink_offer` lookups, keyed by the + * lowercased nip05 address (NIP-05 identifiers are case-insensitive). Without it, every + * profile visit (and every relay-pushed kind-0 refresh while a profile is open) would + * re-fetch the domain's nostr.json. Caches "no offer" results too so profiles without one + * aren't re-hit. A [ResolvedClinkOffer] wrapper holds the nullable parsed pointer * (LruCache can't store nulls); absence means "not fetched yet". */ private class ResolvedClinkOffer( - val noffer: String?, + val noffer: NOffer?, ) private val clinkOfferNip05Cache = LruCache(256) @@ -430,16 +431,16 @@ private fun DisplayClinkOffer( offer = if (id != null && nip05 != null) { // Distinguish "cache miss" from a cached "no offer" (null) so we don't refetch. - val cached = clinkOfferNip05Cache.get(nip05) - val nofferStr = - if (cached != null) { - cached.noffer - } else { - val fetched = withContext(Dispatchers.IO) { accountViewModel.nip05ClientBuilder().loadClinkOffer(id) } - clinkOfferNip05Cache.put(nip05, ResolvedClinkOffer(fetched)) - fetched - } - nofferStr?.let { ClinkPointerParser.parse(it) as? NOffer } + val cacheKey = nip05.lowercase() + val cached = clinkOfferNip05Cache.get(cacheKey) + if (cached != null) { + cached.noffer + } else { + val fetched = withContext(Dispatchers.IO) { accountViewModel.nip05ClientBuilder().loadClinkOffer(id) } + val parsed = fetched?.let { ClinkPointerParser.parse(it) as? NOffer } + clinkOfferNip05Cache.put(cacheKey, ResolvedClinkOffer(parsed)) + parsed + } } else { null }