From ad675fb6deb170be91afd996ea9440467a1bc898 Mon Sep 17 00:00:00 2001 From: scanash00 Date: Fri, 12 Jun 2026 12:01:07 -0800 Subject: [PATCH] Profile stats from microcosm, fix verification badges everywhere, drop post count UI Profile stats: - hydrateProfile no longer calls the AppView. followersCount from Constellation backlinks; followsCount from counting the actor's own graph.follow records. postsCount dropped (can't be counted reliably), and the posts metric is removed from the profile header UI. Verification badges: - hydrateProfileBasic now populates too, so badges show in feeds, threads, lists, search and post authors - not just on the full profile page, which is why they looked buggy/inconsistent. - Verification state is cached per author (5 min TTL, in-flight dedupe) so the same author across a feed doesn't re-run the Constellation lookups, and a transient failure isn't cached (so a badge isn't suppressed for the whole TTL). --- src/lib/microcosm/hydrate.ts | 54 +++++++++++++++++++++++++- src/screens/Profile/Header/Metrics.tsx | 6 --- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/src/lib/microcosm/hydrate.ts b/src/lib/microcosm/hydrate.ts index 06441f9..82a30f7 100644 --- a/src/lib/microcosm/hydrate.ts +++ b/src/lib/microcosm/hydrate.ts @@ -469,6 +469,50 @@ async function hydrateVerificationState( } } +/** + * Cached `hydrateVerificationState`. The same author recurs across every post in + * a feed/thread, so without caching each one would re-run the Constellation + * backlink + trusted-verifier lookups. 5 minute TTL; keyed by did+handle (the + * handle affects isValid). + */ +const verificationCache = new Map< + string, + {value: AppBskyActorDefs.VerificationState | undefined; at: number} +>() +const verificationInflight = new Map< + string, + Promise +>() +const VERIFICATION_TTL = 5 * 60 * 1000 + +function getVerificationState( + did: string, + currentHandle: string, + signal?: AbortSignal, +): Promise { + const key = `${did}|${currentHandle}` + const hit = verificationCache.get(key) + if (hit && Date.now() - hit.at < VERIFICATION_TTL) { + return Promise.resolve(hit.value) + } + const pending = verificationInflight.get(key) + if (pending) return pending + const p = hydrateVerificationState(did, currentHandle, signal) + .then(value => { + // Only cache real results, so a transient failure doesn't suppress a + // badge for the whole TTL. + verificationCache.set(key, {value, at: Date.now()}) + verificationInflight.delete(key) + return value + }) + .catch(() => { + verificationInflight.delete(key) + return undefined + }) + verificationInflight.set(key, p) + return p +} + /** * Hydrate a `ProfileViewDetailed` for a DID or handle, fully from microcosm. * @@ -508,7 +552,7 @@ export async function hydrateProfile( ) .then(r => r.count) .catch(() => undefined), - hydrateVerificationState(did, handle, signal).catch(() => undefined), + getVerificationState(did, handle, signal), ]) const profile = recordRes?.value as AppBskyActorProfile.Record | undefined @@ -740,6 +784,13 @@ export async function hydrateProfileBasic( signal, ).catch(() => undefined) const profile = recordRes?.value as AppBskyActorProfile.Record | undefined + // Verification badge: cached per author so feeds/threads (which repeat the + // same authors) don't re-resolve it for every post. + const verification = await getVerificationState( + mini.did, + mini.handle, + signal, + ) return { $type: 'app.bsky.actor.defs#profileViewBasic', did: mini.did, @@ -747,6 +798,7 @@ export async function hydrateProfileBasic( displayName: profile?.displayName, avatar: cdnUrl('avatar_thumbnail', mini.did, profile?.avatar), labels: extractSelfLabels(mini.did, profile), + verification, } } diff --git a/src/screens/Profile/Header/Metrics.tsx b/src/screens/Profile/Header/Metrics.tsx index 45f0959..7c5068c 100644 --- a/src/screens/Profile/Header/Metrics.tsx +++ b/src/screens/Profile/Header/Metrics.tsx @@ -52,12 +52,6 @@ export function ProfileHeaderMetrics({ {pluralizedFollowings} - - {formatCount(i18n, profile.postsCount || 0)}{' '} - - {plural(profile.postsCount || 0, {one: 'post', other: 'posts'})} - - ) } -- 2.51.2