a tool for shared writing and social publishing
0

Configure Feed

Select the types of activity you want to include in your feed.

more profile read fixes

Jared Pereira (May 27, 2026, 5:25 PM EDT) 63b5e6cd 5cc5627d

+24 -68
+3 -30
app/(app)/lish/[did]/[publication]/dashboard/PublicationSubscribers.tsx
··· 45 45 let byDid = new Map<string, MergedSubscriber>(); 46 46 let emailOnly: MergedSubscriber[] = []; 47 47 for (let s of atprotoSubs) { 48 - let did = s.identities?.bsky_profiles?.did; 48 + let did = s.identities?.atp_did ?? undefined; 49 49 if (!did) continue; 50 50 byDid.set(did, { 51 51 key: `did:${did}`, 52 52 did, 53 - handle: s.identities?.bsky_profiles?.handle ?? undefined, 53 + handle: undefined, 54 54 email: undefined, 55 55 created_at: s.created_at, 56 56 status: "subscribed", ··· 72 72 emailOnly.push({ 73 73 key: `email:${s.id}`, 74 74 did: linkedDid, 75 - handle: s.identities?.bsky_profiles?.handle ?? undefined, 75 + handle: undefined, 76 76 email: s.email, 77 77 created_at: s.created_at, 78 78 status, ··· 176 176 <hr className="border-border-light mt-2 last:hidden" /> 177 177 </div> 178 178 ))} 179 - </div> 180 - </div> 181 - ); 182 - } 183 - 184 - export function SubscribersListSkeleton(props: { showPageBackground?: boolean }) { 185 - return ( 186 - <div 187 - className={`rounded-md ${props.showPageBackground ? "border-border-light p-2" : "border-transparent"}`} 188 - style={ 189 - props.showPageBackground 190 - ? { 191 - backgroundColor: "rgba(var(--bg-page), var(--bg-page-alpha)) ", 192 - } 193 - : { backgroundColor: "transparent" } 194 - } 195 - > 196 - <div className="flex gap-2 flex-col"> 197 - {[0, 1, 2, 3].map((i) => ( 198 - <div 199 - key={i} 200 - className="flex flex-row justify-between gap-2 w-full animate-pulse" 201 - > 202 - <div className="h-5 w-40 bg-border-light rounded-md" /> 203 - <div className="h-5 w-16 bg-border-light rounded-md" /> 204 - </div> 205 - ))} 206 179 </div> 207 180 </div> 208 181 );
+2 -2
app/(app)/lish/[did]/[publication]/dashboard/SubscribersList.tsx
··· 37 37 38 38 const dids = new Set<string>(); 39 39 for (const s of atprotoSubs) { 40 - const d = s.identities?.bsky_profiles?.did; 40 + const d = s.identities?.atp_did ?? undefined; 41 41 if (d) dids.add(d); 42 42 } 43 43 for (const s of emailSubs) { ··· 50 50 const emailOnly: MergedSubscriber[] = []; 51 51 52 52 for (const s of atprotoSubs) { 53 - const d = s.identities?.bsky_profiles?.did; 53 + const d = s.identities?.atp_did ?? undefined; 54 54 if (!d) continue; 55 55 const p = profiles.get(d); 56 56 byDid.set(d, {
+1 -2
app/(app)/lish/[did]/[publication]/dashboard/subs/page.tsx
··· 1 1 import { Suspense } from "react"; 2 2 import { SubsPageShell } from "../SubsPageShell"; 3 3 import { SubscribersList } from "../SubscribersList"; 4 - import { SubscribersListSkeleton } from "../PublicationSubscribers"; 5 4 6 5 export default async function SubsPage(props: { 7 6 params: Promise<{ did: string; publication: string }>; ··· 12 11 13 12 return ( 14 13 <SubsPageShell> 15 - <Suspense fallback={<SubscribersListSkeleton />}> 14 + <Suspense fallback={null}> 16 15 <SubscribersList did={did} publication={publication} /> 17 16 </Suspense> 18 17 </SubsPageShell>
+5 -7
app/(app)/merge-accounts/page.tsx
··· 11 11 PENDING_MERGE_TOKEN_COOKIE, 12 12 resolveAuthToken, 13 13 } from "src/auth"; 14 + import { getProfiles } from "src/identity"; 14 15 15 16 type SearchParams = { [key: string]: string | string[] | undefined }; 16 17 ··· 59 60 ); 60 61 } 61 62 62 - const [{ data: bsky }, { count: docCount }] = await Promise.all([ 63 - supabaseServerClient 64 - .from("bsky_profiles") 65 - .select("handle") 66 - .eq("did", target.identity.atp_did!) 67 - .maybeSingle(), 63 + const [profiles, { count: docCount }] = await Promise.all([ 64 + getProfiles([target.identity.atp_did!]), 68 65 supabaseServerClient 69 66 .from("permission_token_on_homepage") 70 67 .select("token", { count: "exact", head: true }) 71 68 .eq("identity", source.identity.id) 72 69 .not("archived", "is", true), 73 70 ]); 74 - const targetHandle = bsky?.handle ?? target.identity.atp_did!; 71 + const targetHandle = 72 + profiles.get(target.identity.atp_did!)?.handle ?? target.identity.atp_did!; 75 73 const sourceEmail = source.identity.email!; 76 74 const targetEmail = target.identity.email; 77 75 const documents = docCount ?? 0;
+4 -7
app/api/inngest/functions/send_post_broadcast.ts
··· 15 15 resolveReplyToEmail, 16 16 } from "src/utils/newsletterSender"; 17 17 import { PubLeafletPagesLinearDocument } from "lexicons/api"; 18 + import { getProfiles } from "src/identity"; 18 19 import type { Json } from "supabase/database.types"; 19 20 20 21 const BATCH_SIZE = 500; ··· 48 49 const authorDid = new AtUri(document_uri).host; 49 50 50 51 const loaded = await step.run("load-pub-and-doc", async () => { 51 - const [pubRes, docRes, profileRes] = await Promise.all([ 52 + const [pubRes, docRes, profiles] = await Promise.all([ 52 53 supabaseServerClient 53 54 .from("publications") 54 55 .select( ··· 61 62 .select("data") 62 63 .eq("uri", document_uri) 63 64 .maybeSingle(), 64 - supabaseServerClient 65 - .from("bsky_profiles") 66 - .select("handle") 67 - .eq("did", authorDid) 68 - .maybeSingle(), 65 + getProfiles([authorDid]), 69 66 ]); 70 67 return { 71 68 pub: pubRes.data, 72 69 doc: docRes.data, 73 - profile: profileRes.data, 70 + profile: profiles.get(authorDid) ?? null, 74 71 }; 75 72 }); 76 73
+3 -3
app/api/rpc/[command]/get_publication_data.ts
··· 32 32 publication_name, 33 33 ).toString(); 34 34 } 35 - let { data: publication, error } = await supabase 35 + let { data: publication } = await supabase 36 36 .from("publications") 37 37 .select( 38 38 `*, ··· 43 43 recommends_on_documents(count), 44 44 publication_post_sends(status, subscriber_count) 45 45 )), 46 - publication_subscriptions(*, identities(bsky_profiles(*))), 47 - publication_email_subscribers(*, identities(atp_did, bsky_profiles(*))), 46 + publication_subscriptions(*, identities(atp_did)), 47 + publication_email_subscribers(*, identities(atp_did)), 48 48 publication_domains(*), 49 49 publication_newsletter_settings(enabled, reply_to_email, reply_to_verified_at), 50 50 leaflets_in_publications(*,
+2 -2
app/api/rpc/[command]/get_publication_subscribers_timeseries.ts
··· 47 47 const [{ data: atprotoSubs }, { data: emailSubs }] = await Promise.all([ 48 48 supabase 49 49 .from("publication_subscriptions") 50 - .select("created_at, identities(bsky_profiles(did))") 50 + .select("created_at, identities(atp_did)") 51 51 .eq("publication", publication_uri), 52 52 newsletterEnabled 53 53 ? supabase ··· 63 63 // matching the UI merge in PublicationSubscribers.tsx. 64 64 const subscribers = new Map<string, string>(); 65 65 for (const s of atprotoSubs || []) { 66 - const did = s.identities?.bsky_profiles?.did; 66 + const did = s.identities?.atp_did; 67 67 if (!did) continue; 68 68 subscribers.set(`did:${did}`, s.created_at); 69 69 }
+4 -15
app/api/rpc/[command]/get_standard_site_posts.ts
··· 8 8 type NormalizedDocument, 9 9 type NormalizedPublication, 10 10 } from "src/utils/normalizeRecords"; 11 + import { getProfiles } from "src/identity"; 11 12 12 13 export type StandardSitePostData = { 13 14 uri: string; ··· 66 67 ), 67 68 ); 68 69 69 - const { data: profiles } = dids.length 70 - ? await supabase 71 - .from("bsky_profiles") 72 - .select("did, handle, record") 73 - .in("did", dids) 74 - : { data: [] as { did: string; handle: string | null; record: unknown }[] }; 75 - 76 - const profileByDid = new Map( 77 - (profiles || []).map((p) => [p.did, p] as const), 78 - ); 70 + const profiles = await getProfiles(dids); 79 71 80 72 const posts: StandardSitePostData[] = (documents || []) 81 73 .map((d): StandardSitePostData | null => { ··· 93 85 } catch { 94 86 did = null; 95 87 } 96 - const profile = did ? profileByDid.get(did) : undefined; 97 - const profileRecord = (profile?.record ?? null) as 98 - | { displayName?: string } 99 - | null; 88 + const profile = did ? profiles.get(did) : null; 100 89 const author = did 101 90 ? { 102 91 did, 103 92 handle: profile?.handle ?? null, 104 - displayName: profileRecord?.displayName ?? null, 93 + displayName: profile?.displayName ?? null, 105 94 } 106 95 : null; 107 96