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 })