diff --git a/src/components/AccountList/index.tsx b/src/components/AccountList/index.tsx
index bcce7313..2a243dbc 100644
--- a/src/components/AccountList/index.tsx
+++ b/src/components/AccountList/index.tsx
@@ -6,7 +6,7 @@ import { formatPubkey } from '@/lib/pubkey'
import { cn } from '@/lib/utils'
import { useNostr } from '@/providers/NostrProvider'
import { TAccountPointer, TSignerType } from '@/types'
-import { Loader, Trash2 } from 'lucide-react'
+import { Trash2 } from 'lucide-react'
import { useState } from 'react'
import { SimpleUserAvatar } from '../UserAvatar'
import { SimpleUsername } from '../Username'
diff --git a/src/components/FollowButton/index.tsx b/src/components/FollowButton/index.tsx
index 416a9ba4..2d685fcd 100644
--- a/src/components/FollowButton/index.tsx
+++ b/src/components/FollowButton/index.tsx
@@ -14,7 +14,6 @@ import { Skeleton } from '@/components/ui/skeleton'
import { useFollowList } from '@/providers/FollowListProvider'
import { useMuteList } from '@/providers/MuteListProvider'
import { useNostr } from '@/providers/NostrProvider'
-import { Loader } from 'lucide-react'
import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
diff --git a/src/components/MailboxSetting/DiscoveredRelays.tsx b/src/components/MailboxSetting/DiscoveredRelays.tsx
index 60352169..dd09ce57 100644
--- a/src/components/MailboxSetting/DiscoveredRelays.tsx
+++ b/src/components/MailboxSetting/DiscoveredRelays.tsx
@@ -5,7 +5,7 @@ import { normalizeUrl, isLocalNetworkUrl } from '@/lib/url'
import { getRelaysFromNip07Extension, verifyNip05 } from '@/lib/nip05'
import { useNostr } from '@/providers/NostrProvider'
import { TMailboxRelay } from '@/types'
-import { Loader2, Check, AlertCircle } from 'lucide-react'
+import { Check, AlertCircle } from 'lucide-react'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import RelayIcon from '../RelayIcon'
diff --git a/src/components/MailboxSetting/SaveButton.tsx b/src/components/MailboxSetting/SaveButton.tsx
index a5b83d1b..9915257c 100644
--- a/src/components/MailboxSetting/SaveButton.tsx
+++ b/src/components/MailboxSetting/SaveButton.tsx
@@ -4,7 +4,7 @@ import { createRelayListDraftEvent } from '@/lib/draft-event'
import { showPublishingFeedback, showSimplePublishSuccess, showPublishingError } from '@/lib/publishing-feedback'
import { useNostr } from '@/providers/NostrProvider'
import { TMailboxRelay } from '@/types'
-import { CloudUpload, Loader } from 'lucide-react'
+import { CloudUpload } from 'lucide-react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import logger from '@/lib/logger'
diff --git a/src/components/MuteButton/index.tsx b/src/components/MuteButton/index.tsx
index 9c8af6b2..89e025d9 100644
--- a/src/components/MuteButton/index.tsx
+++ b/src/components/MuteButton/index.tsx
@@ -10,7 +10,7 @@ import {
import { useMuteList } from '@/providers/MuteListProvider'
import { useNostr } from '@/providers/NostrProvider'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
-import { BellOff, Loader } from 'lucide-react'
+import { BellOff } from 'lucide-react'
import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
diff --git a/src/components/Note/PublicationIndex/PublicationIndex.tsx b/src/components/Note/PublicationIndex/PublicationIndex.tsx
index 34722734..12a180f2 100644
--- a/src/components/Note/PublicationIndex/PublicationIndex.tsx
+++ b/src/components/Note/PublicationIndex/PublicationIndex.tsx
@@ -10,6 +10,7 @@ import client from '@/services/client.service'
import { eventService, queryService, replaceableEventService } from '@/services/client.service'
import logger from '@/lib/logger'
import { Button } from '@/components/ui/button'
+import { Skeleton } from '@/components/ui/skeleton'
import { RefreshCw, ArrowUp } from 'lucide-react'
import indexedDb from '@/services/indexed-db.service'
import { isReplaceableEvent } from '@/lib/event'
@@ -1442,7 +1443,11 @@ export default function PublicationIndex({
onClick={handleManualRetry}
disabled={isRetrying}
>
-
+ {isRetrying ? (
+
+ ) : (
+
+ )}
Retry All
@@ -1466,7 +1471,11 @@ export default function PublicationIndex({
onClick={handleManualRetry}
disabled={isRetrying}
>
-
+ {isRetrying ? (
+
+ ) : (
+
+ )}
Retry Loading
@@ -1527,7 +1536,11 @@ export default function PublicationIndex({
disabled={isRetrying}
className="shrink-0"
>
-
+ {isRetrying ? (
+
+ ) : (
+
+ )}
Retry
diff --git a/src/components/NoteStats/Likes.tsx b/src/components/NoteStats/Likes.tsx
index 5d87984e..60a0a972 100644
--- a/src/components/NoteStats/Likes.tsx
+++ b/src/components/NoteStats/Likes.tsx
@@ -10,7 +10,6 @@ import { useNostr } from '@/providers/NostrProvider'
import { useUserTrust } from '@/providers/UserTrustProvider'
import noteStatsService from '@/services/note-stats.service'
import { TEmoji } from '@/types'
-import { Loader } from 'lucide-react'
import { Event } from 'nostr-tools'
import { useMemo, useRef, useState } from 'react'
import Emoji from '../Emoji'
diff --git a/src/components/Profile/ProfileMediaFeed.tsx b/src/components/Profile/ProfileMediaFeed.tsx
index 310899d2..a61a8094 100644
--- a/src/components/Profile/ProfileMediaFeed.tsx
+++ b/src/components/Profile/ProfileMediaFeed.tsx
@@ -2,9 +2,7 @@ import NoteList, { type TNoteListRef } from '@/components/NoteList'
import { buildProfilePageReadRelayUrls } from '@/lib/favorites-feed-relays'
import { computeSpellSubRequestsIdentityKey } from '@/lib/spell-feed-request-identity'
import {
- applyFauxSpellCapsToSubRequests,
- appendCuratedReadOnlyRelays,
- buildProfileMediaSpellFilter,
+ buildProfileMediaSubRequests,
MEDIA_SPELL_KINDS,
PROFILE_MEDIA_REQ_LIMIT
} from '@/pages/primary/SpellsPage/fauxSpellFeeds'
@@ -58,11 +56,7 @@ const ProfileMediaFeed = forwardRef(({ pubkey
const subRequests = useMemo(() => {
const pk = pubkey?.trim()
if (!pk || profileRelayUrls === null) return []
- const urls = appendCuratedReadOnlyRelays(profileRelayUrls, blockedRelays)
- if (!urls.length) return []
- return applyFauxSpellCapsToSubRequests([
- { urls, filter: buildProfileMediaSpellFilter(pk) }
- ])
+ return buildProfileMediaSubRequests(profileRelayUrls, blockedRelays, pk)
}, [pubkey, profileRelayUrls, blockedRelays])
const feedSubscriptionKey = useMemo(
diff --git a/src/components/RefreshButton/index.tsx b/src/components/RefreshButton/index.tsx
index 556f944b..f65de2b9 100644
--- a/src/components/RefreshButton/index.tsx
+++ b/src/components/RefreshButton/index.tsx
@@ -1,5 +1,5 @@
import { Button } from '@/components/ui/button'
-import { cn } from '@/lib/utils'
+import { Skeleton } from '@/components/ui/skeleton'
import { RefreshCcw } from 'lucide-react'
import { useState } from 'react'
@@ -18,7 +18,11 @@ export function RefreshButton({ onClick }: { onClick: () => void }) {
}}
className="text-muted-foreground focus:text-foreground [&_svg]:size-3 h-8 px-2 text-xs"
>
-
+ {refreshing ? (
+
+ ) : (
+
+ )}
)
}
diff --git a/src/pages/primary/SpellsPage/fauxSpellFeeds.ts b/src/pages/primary/SpellsPage/fauxSpellFeeds.ts
index 9d90acd7..f38a55a8 100644
--- a/src/pages/primary/SpellsPage/fauxSpellFeeds.ts
+++ b/src/pages/primary/SpellsPage/fauxSpellFeeds.ts
@@ -8,7 +8,8 @@
* topics go in a single `#t` filter (NIP-01 OR semantics). The notifications spell uses a narrow
* kind list vs full profile kinds.
*/
-import { ExtendedKind, PROFILE_FEED_KINDS, READ_ONLY_RELAY_URLS } from '@/constants'
+import { ExtendedKind, FAST_READ_RELAY_URLS, PROFILE_FEED_KINDS, READ_ONLY_RELAY_URLS } from '@/constants'
+import { mergeRelayUrlLayers } from '@/lib/favorites-feed-relays'
import { normalizeTopic } from '@/lib/discussion-topics'
import { normalizeUrl } from '@/lib/url'
import type { TFeedSubRequest } from '@/types'
@@ -21,6 +22,13 @@ export const FAUX_SPELL_EVENT_LIMIT = 200
/** Profile Media tab: single REQ `limit` (matches merged cap in NoteList one-shot). */
export const PROFILE_MEDIA_REQ_LIMIT = 200
+/**
+ * More sockets than {@link FAUX_SPELL_MAX_RELAYS}: profile media must query read aggregators plus the
+ * author stack. {@link appendCuratedReadOnlyRelays} + {@link applyFauxSpellCapsToSubRequests} used to put
+ * aggr *after* six NIP-65 relays, then slice to six — so aggr was never hit and media was often missing.
+ */
+export const PROFILE_MEDIA_MAX_RELAYS = 16
+
/**
* Trim relay lists and filter limits (and bookmark `ids`) so faux feeds stay cheap to open.
*/
@@ -123,6 +131,20 @@ export function buildProfileMediaSpellFilter(pubkey: string): Filter {
}
}
+/** Read-only + {@link FAST_READ_RELAY_URLS} before the author’s six-relay stack so major mirrors are always queried. */
+export function buildProfileMediaSubRequests(
+ profileRelayUrls: string[],
+ blockedRelays: string[],
+ pubkey: string
+): TFeedSubRequest[] {
+ const readOnlyLayer = READ_ONLY_RELAY_URLS.map((u) => normalizeUrl(u) || u).filter(Boolean)
+ const fastReadLayer = FAST_READ_RELAY_URLS.map((u) => normalizeUrl(u) || u).filter(Boolean)
+ const merged = mergeRelayUrlLayers([readOnlyLayer, fastReadLayer, profileRelayUrls], blockedRelays)
+ const urls = merged.slice(0, PROFILE_MEDIA_MAX_RELAYS)
+ if (!urls.length) return []
+ return [{ urls, filter: buildProfileMediaSpellFilter(pubkey) }]
+}
+
export function buildCalendarSpellFilter(): Filter {
return {
kinds: [ExtendedKind.CALENDAR_EVENT_DATE, ExtendedKind.CALENDAR_EVENT_TIME],
diff --git a/src/pages/secondary/MuteListPage/index.tsx b/src/pages/secondary/MuteListPage/index.tsx
index cf91dc72..b6270249 100644
--- a/src/pages/secondary/MuteListPage/index.tsx
+++ b/src/pages/secondary/MuteListPage/index.tsx
@@ -10,7 +10,7 @@ import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
import { usePrimaryNoteView } from '@/PageManager'
import { useMuteList } from '@/providers/MuteListProvider'
import { useNostr } from '@/providers/NostrProvider'
-import { Loader, Lock, Unlock } from 'lucide-react'
+import { Lock, Unlock } from 'lucide-react'
import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import NotFoundPage from '../NotFoundPage'
diff --git a/src/pages/secondary/RssFeedSettingsPage/index.tsx b/src/pages/secondary/RssFeedSettingsPage/index.tsx
index 561db8c3..4cd390ac 100644
--- a/src/pages/secondary/RssFeedSettingsPage/index.tsx
+++ b/src/pages/secondary/RssFeedSettingsPage/index.tsx
@@ -14,7 +14,7 @@ import { Switch } from '@/components/ui/switch'
import storage from '@/services/local-storage.service'
import { createRssFeedListDraftEvent } from '@/lib/draft-event'
import { showPublishingFeedback, showSimplePublishSuccess, showPublishingError } from '@/lib/publishing-feedback'
-import { CloudUpload, Loader, Trash2, Plus, Download, Upload } from 'lucide-react'
+import { CloudUpload, Trash2, Plus, Download, Upload } from 'lucide-react'
import logger from '@/lib/logger'
import { queryService } from '@/services/client.service'
import indexedDb from '@/services/indexed-db.service'
diff --git a/src/pages/secondary/WalletPage/LightningAddressInput.tsx b/src/pages/secondary/WalletPage/LightningAddressInput.tsx
index 4240b7bf..fb6deece 100644
--- a/src/pages/secondary/WalletPage/LightningAddressInput.tsx
+++ b/src/pages/secondary/WalletPage/LightningAddressInput.tsx
@@ -5,7 +5,6 @@ import { Label } from '@/components/ui/label'
import { createProfileDraftEvent } from '@/lib/draft-event'
import { isEmail } from '@/lib/utils'
import { useNostr } from '@/providers/NostrProvider'
-import { Loader } from 'lucide-react'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
diff --git a/src/services/client.service.ts b/src/services/client.service.ts
index 0ce63eb8..ec907524 100644
--- a/src/services/client.service.ts
+++ b/src/services/client.service.ts
@@ -151,7 +151,7 @@ class ClientService extends EventTarget {
private async prewarmProfileSearchIndexFromIdb(): Promise {
const t0 = typeof performance !== 'undefined' ? performance.now() : 0
let profileRows = 0
- await indexedDb.iterateProfileEvents((profileEvent) => {
+ await indexedDb.iterateProfileEvents(async (profileEvent) => {
this.addUsernameToIndex(profileEvent)
profileRows += 1
})