diff --git a/src/components/Nip05/index.tsx b/src/components/Nip05/index.tsx
index 2a9bc9a2..78044ea3 100644
--- a/src/components/Nip05/index.tsx
+++ b/src/components/Nip05/index.tsx
@@ -6,12 +6,19 @@ import { SecondaryPageLink } from '@/PageManager'
import { BadgeAlert, BadgeCheck } from 'lucide-react'
import { Favicon } from '../Favicon'
-export default function Nip05({ pubkey, append }: { pubkey: string; append?: string }) {
- const { profile } = useFetchProfile(pubkey)
- const { nip05IsVerified, nip05Name, nip05Domain, isFetching } = useFetchNip05(
- profile?.nip05,
- pubkey
- )
+export default function Nip05({
+ pubkey,
+ nip05: nip05Prop,
+ append
+}: {
+ pubkey: string
+ /** When set (e.g. profile page), skip a second {@link useFetchProfile} network pass. */
+ nip05?: string
+ append?: string
+}) {
+ const { profile } = useFetchProfile(nip05Prop === undefined ? pubkey : undefined)
+ const resolvedNip05 = nip05Prop ?? profile?.nip05
+ const { nip05IsVerified, nip05Name, nip05Domain, isFetching } = useFetchNip05(resolvedNip05, pubkey)
if (isFetching) {
return (
@@ -21,7 +28,7 @@ export default function Nip05({ pubkey, append }: { pubkey: string; append?: str
)
}
- if (!profile?.nip05 || !nip05Name || !nip05Domain) return null
+ if (!resolvedNip05 || !nip05Name || !nip05Domain) return null
return (
([])
- const { pubkey: accountPubkey } = useNostr()
-
- useEffect(() => {
- if (!pubkey || !accountPubkey) return
-
- const init = async () => {
- const followListEvent = await replaceableEventService.fetchReplaceableEvent(accountPubkey, kinds.Contacts)
- const followings = followListEvent ? getPubkeysFromPTags(followListEvent.tags).reverse() : []
- const followingsOfFollowings = await Promise.all(
- followings.map(async (following) => {
- const followListEvent = await replaceableEventService.fetchReplaceableEvent(following, kinds.Contacts)
- return followListEvent ? getPubkeysFromPTags(followListEvent.tags) : []
- })
- )
- const _followedBy: string[] = []
- const limit = isSmallScreen ? 3 : 5
- for (const [index, following] of followings.entries()) {
- if (following === pubkey) continue
- if (followingsOfFollowings[index].includes(pubkey)) {
- _followedBy.push(following)
- }
- if (_followedBy.length >= limit) {
- break
- }
- }
- setFollowedBy(_followedBy)
- }
- init()
- }, [pubkey, accountPubkey])
-
- if (followedBy.length === 0) return null
-
- return (
-
-
{t('Followed by')}
- {followedBy.map((p) => (
-
- ))}
-
- )
-}
diff --git a/src/components/Profile/index.tsx b/src/components/Profile/index.tsx
index 1b6ca986..9a4e9898 100644
--- a/src/components/Profile/index.tsx
+++ b/src/components/Profile/index.tsx
@@ -62,7 +62,6 @@ import { useTranslation } from 'react-i18next'
import logger from '@/lib/logger'
import { AlexandriaEventsSearchEmptyCta } from '@/components/AlexandriaEventsSearchEmptyCta'
import NotFound from '../NotFound'
-import FollowedBy from './FollowedBy'
import ProfileBadges from './ProfileBadges'
import ProfileFeed from './ProfileFeed'
import ProfileReportsDialog from './ProfileReportsDialog'
@@ -195,9 +194,14 @@ export default function Profile({
}, [syncAuthorReplaceablesFromCache])
useEffect(() => {
- if (!profile?.pubkey) return
- void client.refreshAuthorPublishedReplaceablesOnProfileView(profile.pubkey)
- }, [profile?.pubkey])
+ if (!profile?.pubkey || profile.batchPlaceholder) return
+ const pk = profile.pubkey
+ // Defer wide replaceable refresh so initial kind-0 / feed relay setup can finish first.
+ const timer = window.setTimeout(() => {
+ void client.refreshAuthorPublishedReplaceablesOnProfileView(pk)
+ }, 2_000)
+ return () => clearTimeout(timer)
+ }, [profile?.pubkey, profile?.batchPlaceholder])
useEffect(() => {
if (!isSelf || !profile?.pubkey || !accountProfileEvent) return
@@ -213,7 +217,7 @@ export default function Profile({
const onAuthorReplaceablesRefreshed: EventListener = (domEvt) => {
const detailPk = (domEvt as CustomEvent<{ pubkey?: string }>).detail?.pubkey?.toLowerCase()
if (detailPk !== pk) return
- void syncAuthorReplaceablesFromCache(profile.pubkey, { bustCache: true })
+ void syncAuthorReplaceablesFromCache(profile.pubkey)
}
window.addEventListener(
ReplaceableEventService.AUTHOR_REPLACEABLES_REFRESHED_EVENT,
@@ -226,10 +230,6 @@ export default function Profile({
)
}, [profile?.pubkey, syncAuthorReplaceablesFromCache])
- const isFollowingYou = useMemo(() => {
- // This will be handled by the FollowedBy component
- return false
- }, [profile, accountPubkey])
const defaultImage = useMemo(
() => (profile?.pubkey ? generateImageByPubkey(profile?.pubkey) : ''),
[profile]
@@ -524,13 +524,8 @@ export default function Profile({
{username}
- {isFollowingYou && (
-
- {t('Follows you')}
-
- )}
-
+
{/* Display multiple NIP-05 values if available, with verification */}
{nip05List && nip05List.length > 1 && (
@@ -608,13 +603,10 @@ export default function Profile({
defaultLightningAddress={zapLightningDefault}
prefetchedPayment={prefetchedZapPayment}
/>
-
-
-
-
- {isSelf && }
-
- {!isSelf &&
}
+
+
+
+ {isSelf && }
diff --git a/src/constants.ts b/src/constants.ts
index e1095a6e..69c6ed60 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -406,7 +406,7 @@ export const NIP66_DISCOVERY_RELAY_URLS = [
// Relay with bookstr composite index support
export const BOOKSTR_RELAY_URLS = [
- 'wss://orly-relay.imwald.eu'
+ 'wss://thecitadel.nostr1.com'
]
/**
@@ -469,7 +469,6 @@ export const FAST_READ_RELAY_URLS = [
'wss://theforest.nostr1.com',
'wss://nostr.land',
'wss://nostr.wine',
- 'wss://orly-relay.imwald.eu',
'wss://nostr21.com'
]
diff --git a/src/hooks/useFetchProfile.tsx b/src/hooks/useFetchProfile.tsx
index c01acfa0..9dbfa7dc 100644
--- a/src/hooks/useFetchProfile.tsx
+++ b/src/hooks/useFetchProfile.tsx
@@ -691,6 +691,8 @@ export function useFetchProfile(id?: string, skipCache = false) {
const onAuthorReplaceablesRefreshed: EventListener = (domEvt) => {
const detailPk = (domEvt as CustomEvent<{ pubkey?: string }>).detail?.pubkey?.toLowerCase()
if (detailPk !== pkLowerResolved) return
+ // Background profile-view refresh already persisted kind 0 — avoid a second full fetchProfileEvent pass.
+ if (initializedPubkeysRef.current.has(pkLowerResolved)) return
void checkProfile(pkLowerResolved, { current: profileRefreshCancelledRef.current })
}
window.addEventListener(
diff --git a/src/hooks/useProfileAuthorFeedSubRequests.ts b/src/hooks/useProfileAuthorFeedSubRequests.ts
index 3c71408a..d8b75a1a 100644
--- a/src/hooks/useProfileAuthorFeedSubRequests.ts
+++ b/src/hooks/useProfileAuthorFeedSubRequests.ts
@@ -88,21 +88,35 @@ export function useProfileAuthorFeedSubRequests({
let cancelled = false
const socialKinds = kinds.some(isSocialKindBlockedKind)
+ const applyRelayList = (authorRl: typeof emptyAuthor) => {
+ const urls = buildProfilePageReadRelayUrls(
+ favoriteRelays,
+ blockedRelays,
+ authorRl,
+ socialKinds,
+ includeAuthorLocalRelays,
+ kinds,
+ useGlobalRelayBootstrap
+ )
+ if (urls.length > 0) {
+ setRelayUrls(urls)
+ }
+ }
+
+ void client
+ .peekRelayListFromStorage(pubkey)
+ .then((cached) => {
+ if (cancelled) return
+ applyRelayList(cached)
+ })
+ .catch(() => {})
+
void client
.fetchRelayList(pubkey)
.catch(() => emptyAuthor)
.then((authorRl) => {
if (cancelled) return
- const urls = buildProfilePageReadRelayUrls(
- favoriteRelays,
- blockedRelays,
- authorRl,
- socialKinds,
- includeAuthorLocalRelays,
- kinds,
- useGlobalRelayBootstrap
- )
- setRelayUrls(urls)
+ applyRelayList(authorRl)
})
return () => {
diff --git a/src/services/client-replaceable-events.service.ts b/src/services/client-replaceable-events.service.ts
index dbc826e1..7f252a3f 100644
--- a/src/services/client-replaceable-events.service.ts
+++ b/src/services/client-replaceable-events.service.ts
@@ -87,6 +87,8 @@ export class ReplaceableEventService {
})
/** One in-flight profile replaceables pull per author (avoids stacked REQs when profile UI remounts). */
private authorReplaceablesRefreshByPubkey = new Map
>()
+ /** Per-author cooldown after a successful profile-view replaceable sweep (avoids reopen loops). */
+ private authorProfileViewRefreshNotBeforeMs = new Map()
private replaceableEventFromBigRelaysDataloader: DataLoader<
{ pubkey: string; kind: number },
NEvent | null,
@@ -1409,6 +1411,9 @@ export class ReplaceableEventService {
const pk = pubkey.trim().toLowerCase()
if (!/^[0-9a-f]{64}$/.test(pk)) return
+ const notBefore = this.authorProfileViewRefreshNotBeforeMs.get(pk) ?? 0
+ if (Date.now() < notBefore) return
+
const inFlight = this.authorReplaceablesRefreshByPubkey.get(pk)
if (inFlight) return inFlight
@@ -1502,6 +1507,8 @@ export class ReplaceableEventService {
})
)
+ this.authorProfileViewRefreshNotBeforeMs.set(pk, Date.now() + 90_000)
+
if (typeof window !== 'undefined') {
window.dispatchEvent(
new CustomEvent(ReplaceableEventService.AUTHOR_REPLACEABLES_REFRESHED_EVENT, { detail: { pubkey: pk } })