From b66e942efa35984729ff22ca589a11cc8ccb68df Mon Sep 17 00:00:00 2001 From: scanash00 Date: Fri, 12 Jun 2026 11:55:35 -0800 Subject: [PATCH] Show like/reply/repost counts on custom feed posts hydratePostFromRecord (used by the batch custom-feed path) was author-only with no counts, so feed posts rendered without stats. It now optionally resolves like/repost/reply/quote counts and viewer state from Constellation (the post record is already in hand from the batch, so only the count lookups are added, run in parallel). buildCustomFeed enables it. Counts default off, so other callers are unchanged. --- src/lib/microcosm/feed.ts | 5 +++- src/lib/microcosm/hydrate.ts | 51 +++++++++++++++++++++++++++--------- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/src/lib/microcosm/feed.ts b/src/lib/microcosm/feed.ts index 69b8ffb..db14695 100644 --- a/src/lib/microcosm/feed.ts +++ b/src/lib/microcosm/feed.ts @@ -201,7 +201,10 @@ export async function buildCustomFeed( batch.order.map(item => { const rec = batch.records.get(item.post) return rec - ? hydratePostFromRecord(item.post, rec, signal).catch(() => undefined) + ? hydratePostFromRecord(item.post, rec, signal, { + counts: true, + viewerDid: opts.viewerDid, + }).catch(() => undefined) : Promise.resolve(undefined) }), ) diff --git a/src/lib/microcosm/hydrate.ts b/src/lib/microcosm/hydrate.ts index ccdf1aa..e6bc0ed 100644 --- a/src/lib/microcosm/hydrate.ts +++ b/src/lib/microcosm/hydrate.ts @@ -677,28 +677,50 @@ export async function hydratePost( /** * Build a `PostView` from an ALREADY-FETCHED record (e.g. from Slingshot's - * batch feed hydration), resolving only the author. Lite by design: no counts / - * viewer state. This is what makes batched feeds fast — the post records come - * in one request and only author identities remain. + * batch feed hydration), resolving the author and (unless `opts.counts` is + * false) the like/repost/reply/quote counts + viewer state from Constellation. + * The post record is already in hand, so only the author + count lookups remain. */ export async function hydratePostFromRecord( postUri: string, recordRes: {cid?: string; value: unknown}, signal?: AbortSignal, + opts?: {counts?: boolean; viewerDid?: string}, ): Promise { const parsed = parseAtUri(postUri) if (!parsed) return undefined - const author = await hydrateProfileBasic(parsed.did, signal).catch( - () => undefined, - ) + const wantCounts = opts?.counts ?? false + + const [author, likes, reposts, replies, quotes, viewer, embed] = + await Promise.all([ + hydrateProfileBasic(parsed.did, signal).catch(() => undefined), + wantCounts + ? constellation.likeCount(postUri, signal).catch(() => undefined) + : Promise.resolve(undefined), + wantCounts + ? constellation.repostCount(postUri, signal).catch(() => undefined) + : Promise.resolve(undefined), + wantCounts + ? constellation.replyCount(postUri, signal).catch(() => undefined) + : Promise.resolve(undefined), + wantCounts + ? constellation.quoteCount(postUri, signal).catch(() => undefined) + : Promise.resolve(undefined), + wantCounts && opts?.viewerDid + ? hydrateViewerState(postUri, opts.viewerDid, signal).catch( + () => undefined, + ) + : Promise.resolve(undefined), + hydrateEmbed( + parsed.did, + (recordRes.value as AppBskyFeedPost.Record)?.embed, + MAX_QUOTE_DEPTH, + signal, + ).catch(() => undefined) as Promise, + ]) + if (!author) return undefined const record = recordRes.value as AppBskyFeedPost.Record - const embed = (await hydrateEmbed( - parsed.did, - record?.embed, - MAX_QUOTE_DEPTH, - signal, - ).catch(() => undefined)) as AppBskyFeedDefs.PostView['embed'] return { $type: 'app.bsky.feed.defs#postView', uri: postUri, @@ -706,7 +728,12 @@ export async function hydratePostFromRecord( author, record, embed, + likeCount: likes?.total, + repostCount: reposts?.total, + replyCount: replies?.total, + quoteCount: quotes?.total, indexedAt: record?.createdAt ?? new Date().toISOString(), + viewer, } } -- 2.51.2