Browse Source

fix lame feeds

imwald
Silberengel 2 weeks ago
parent
commit
ec64c96143
  1. 5
      src/components/FeedFilterToolbarRow/index.tsx
  2. 17
      src/components/FeedRelaysIconRow/index.tsx
  3. 16
      src/components/KindFilter/index.tsx
  4. 44
      src/components/NoteList/index.tsx
  5. 2
      src/i18n/locales/en.ts
  6. 51
      src/pages/primary/NoteListPage/RelaysFeed.tsx
  7. 11
      src/pages/primary/SpellsPage/index.tsx
  8. 12
      src/pages/primary/SpellsPage/useSpellsPageFeed.ts
  9. 4
      src/services/relay-info.service.ts

5
src/components/FeedFilterToolbarRow/index.tsx

@ -37,7 +37,10 @@ export default function FeedFilterToolbarRow({ @@ -37,7 +37,10 @@ export default function FeedFilterToolbarRow({
{onRefresh != null ? <RefreshButton onClick={onRefresh} /> : null}
<KindFilter showKinds={showKinds} onShowKindsChange={onShowKindsChange} />
{includeFeedSearchSlot ? (
<div ref={feedFilterTabRowSlotRef} className="flex shrink-0 flex-nowrap items-center" />
<div
ref={feedFilterTabRowSlotRef}
className="flex min-w-0 flex-1 flex-nowrap items-center justify-end gap-1 overflow-hidden"
/>
) : null}
</div>
)

17
src/components/FeedRelaysIconRow/index.tsx

@ -9,18 +9,27 @@ import { useTranslation } from 'react-i18next' @@ -9,18 +9,27 @@ import { useTranslation } from 'react-i18next'
export function FeedRelaysIconRow({
urls,
className
className,
compact = false
}: {
urls: readonly string[]
className?: string
/** Smaller icons for inline toolbar rows (e.g. next to the feed filter toggle). */
compact?: boolean
}) {
const { t } = useTranslation()
const { navigateToRelay } = useSmartRelayNavigation()
if (urls.length === 0) return null
const buttonClass = compact
? 'h-5 w-5 min-h-5 min-w-5 shrink-0 rounded-full p-0 hover:bg-muted/80'
: 'h-7 w-7 min-h-7 min-w-7 shrink-0 rounded-full p-0 hover:bg-muted/80'
const iconClass = compact ? 'h-4 w-4' : 'h-6 w-6'
const iconSize = compact ? 8 : 12
return (
<div
className={cn('flex min-w-0 flex-wrap items-center gap-1', className)}
className={cn('flex min-w-0 flex-nowrap items-center', compact ? 'gap-0.5' : 'gap-1', className)}
role="group"
aria-label={t('Feed relays', { defaultValue: 'Relays in this feed' })}
>
@ -36,7 +45,7 @@ export function FeedRelaysIconRow({ @@ -36,7 +45,7 @@ export function FeedRelaysIconRow({
type="button"
variant="ghost"
size="sm"
className="h-7 w-7 min-h-7 min-w-7 shrink-0 rounded-full p-0 hover:bg-muted/80"
className={buttonClass}
title={title}
aria-label={
isTrending
@ -45,7 +54,7 @@ export function FeedRelaysIconRow({ @@ -45,7 +54,7 @@ export function FeedRelaysIconRow({
}
onClick={() => navigateToRelay(toRelay(url))}
>
<RelayIcon url={url} className="h-6 w-6" iconSize={12} />
<RelayIcon url={url} className={iconClass} iconSize={iconSize} />
</Button>
)
})}

16
src/components/KindFilter/index.tsx

@ -18,7 +18,6 @@ import { @@ -18,7 +18,6 @@ import {
} from '@/lib/feed-kind-filter'
import { LIVE_ACTIVITY_KINDS } from '@/lib/live-activities'
import { cn } from '@/lib/utils'
import { useFontSize } from '@/providers/FontSizeProvider'
import { useKindFilterOrDefaults } from '@/providers/KindFilterProvider'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
import { ListFilter } from 'lucide-react'
@ -60,7 +59,6 @@ export default function KindFilter({ @@ -60,7 +59,6 @@ export default function KindFilter({
}) {
const { t } = useTranslation()
const { isSmallScreen } = useScreenSize()
const { fontSize } = useFontSize()
const {
showKinds: savedShowKinds,
showKind1OPs: savedShowKind1OPs,
@ -170,7 +168,7 @@ export default function KindFilter({ @@ -170,7 +168,7 @@ export default function KindFilter({
size="titlebar-icon"
aria-label={t('Filter')}
className={cn(
'relative h-8 w-fit shrink-0 px-1.5 text-xs focus:text-foreground',
'relative shrink-0 focus:text-foreground',
!isDifferentFromSaved && !feedKindFilterBypass && 'text-muted-foreground',
feedKindFilterBypass && 'text-amber-600 dark:text-amber-400'
)}
@ -180,17 +178,9 @@ export default function KindFilter({ @@ -180,17 +178,9 @@ export default function KindFilter({
}
}}
>
<ListFilter className="size-3.5 shrink-0" />
<span
className={cn(
'ml-1 hidden',
isSmallScreen && fontSize === 'large' ? 'min-[400px]:inline' : 'min-[352px]:inline'
)}
>
{t('Filter')}
</span>
<ListFilter className="size-5 shrink-0" />
{isDifferentFromSaved && (
<div className="absolute size-1.5 rounded-full bg-primary left-6 top-1.5 ring-1 ring-background" />
<div className="absolute size-1.5 rounded-full bg-primary right-1.5 top-1.5 ring-1 ring-background" />
)}
</Button>
)

44
src/components/NoteList/index.tsx

@ -4653,14 +4653,17 @@ const NoteList = forwardRef( @@ -4653,14 +4653,17 @@ const NoteList = forwardRef(
</Button>
)
const feedRelayToolbarRow =
feedRelayUrls.length > 0 ? (
<FeedRelaysIconRow
urls={feedRelayUrls}
compact
className="min-w-0 flex-1 overflow-x-auto scrollbar-hide"
/>
) : null
const feedClientFilterPanel = feedClientFilterOpen ? (
<div id="feed-client-filter-panel" className={feedClientFilterPanelSurfaceClass}>
{feedRelayUrls.length > 0 ? (
<div className={feedClientFilterSectionClass}>
<p className="text-sm font-medium">{t('Feed relays', { defaultValue: 'Relays in this feed' })}</p>
<FeedRelaysIconRow urls={feedRelayUrls} />
</div>
) : null}
<div className={feedClientFilterSectionClass}>
<Label htmlFor="feed-client-search" className="text-sm font-medium">
{t('Search loaded posts')}
@ -4839,10 +4842,16 @@ const NoteList = forwardRef( @@ -4839,10 +4842,16 @@ const NoteList = forwardRef(
) : null
const feedClientFilterChrome = feedClientFilterPanelPortalMode ? (
feedClientFilterToggleButton
<div className="flex min-w-0 w-full flex-nowrap items-center gap-1">
{feedRelayToolbarRow}
<div className="shrink-0">{feedClientFilterToggleButton}</div>
</div>
) : (
<>
<div className="flex items-center gap-1">{feedClientFilterToggleButton}</div>
<div className="flex min-w-0 flex-nowrap items-center gap-1 px-0.5">
{feedRelayToolbarRow}
<div className="ml-auto shrink-0">{feedClientFilterToggleButton}</div>
</div>
{feedClientFilterPanel}
</>
)
@ -4870,15 +4879,21 @@ const NoteList = forwardRef( @@ -4870,15 +4879,21 @@ const NoteList = forwardRef(
// Relay-op rows arrive only after every relay in the wave reports terminal state. A slow or
// wedged connection (e.g. NIP-42 re-auth) can delay that indefinitely while events already stream
// in — without this guard the "Looking for more events…" banner never clears.
const showFeedInitialLoading =
listSourceEvents.length === 0 &&
!feedFullSearchActive &&
(loading || (subRequests.length > 0 && !feedTimelineEmptyUiReady))
const showRelaySubscribeWavePendingBanner =
!oneShotFetch &&
!feedFullSearchActive &&
subRequests.length > 0 &&
relayCapabilityReady &&
timelineKey != null &&
feedSubscribeRelayOutcomes.length === 0 &&
feedTimelineEmptyUiReady &&
timelineEventsForFilter.length === 0
timelineEventsForFilter.length === 0 &&
(loading ||
!feedTimelineEmptyUiReady ||
(feedSubscribeRelayOutcomes.length === 0 && feedTimelineEmptyUiReady))
const showProgressiveLayersPendingBanner =
Boolean(progressiveWarmupTrimmed) && progressiveLayersSearching && !feedFullSearchActive
const showLookingForMoreEventsBanner =
@ -4950,9 +4965,7 @@ const NoteList = forwardRef( @@ -4950,9 +4965,7 @@ const NoteList = forwardRef(
/>
))
)}
{listSourceEvents.length === 0 &&
!feedFullSearchActive &&
(loading || (subRequests.length > 0 && !feedTimelineEmptyUiReady)) ? (
{showFeedInitialLoading ? (
<div
ref={bottomRef}
className={gridLayout ? 'grid grid-cols-3 gap-0.5 pr-4 min-h-[40vh]' : 'min-h-[40vh] space-y-2 px-1 py-4'}
@ -4960,6 +4973,9 @@ const NoteList = forwardRef( @@ -4960,6 +4973,9 @@ const NoteList = forwardRef(
aria-live="polite"
aria-busy="true"
>
<p className="col-span-full px-2 pb-2 text-center text-sm text-muted-foreground">
{t('Loading feed…')}
</p>
{gridLayout
? Array.from({ length: 9 }).map((_, i) => (
<div key={i} className="aspect-square animate-pulse bg-muted" />

2
src/i18n/locales/en.ts

@ -1638,7 +1638,9 @@ export default { @@ -1638,7 +1638,9 @@ export default {
'Loading follow list…': 'Loading follow list…',
'Could not load recommended follows': 'Could not load recommended follows',
'Your follow list is empty': 'Your follow list is empty',
'Loading feed…': 'Loading feed…',
'Loading recent posts from follows…': 'Loading recent posts from follows…',
'Loading notifications…': 'Loading notifications…',
'Loading more…': 'Loading more…',
'No recent posts from this user in the current fetch':
'No recent posts from this user in the current fetch',

51
src/pages/primary/NoteListPage/RelaysFeed.tsx

@ -2,6 +2,7 @@ import NormalFeed from '@/components/NormalFeed' @@ -2,6 +2,7 @@ import NormalFeed from '@/components/NormalFeed'
import type { TNoteListRef } from '@/components/NoteList'
import { ensureHomeFeedTrendingRelay } from '@/lib/home-feed-relays'
import { checkAlgoRelay } from '@/lib/relay'
import { dedupeNormalizeRelayUrlsOrdered } from '@/lib/relay-url-priority'
import { normalizeUrl } from '@/lib/url'
import { useFeed } from '@/providers/feed-context'
import { useKindFilterOrDefaults } from '@/providers/KindFilterProvider'
@ -21,7 +22,8 @@ const RelaysFeed = forwardRef< @@ -21,7 +22,8 @@ const RelaysFeed = forwardRef<
const { relayUrls, replyRelayUrls } = useFeed()
const { showKinds } = useKindFilterOrDefaults()
const [areAlgoRelays, setAreAlgoRelays] = useState(false)
const [relayCapabilityReady, setRelayCapabilityReady] = useState(false)
/** Timeline REQs must not wait on NIP-11; cache/IDB serves algo detection in the background. */
const [relayCapabilityReady, setRelayCapabilityReady] = useState(true)
const relayUrlsKey = useMemo(
() =>
@ -41,47 +43,40 @@ const RelaysFeed = forwardRef< @@ -41,47 +43,40 @@ const RelaysFeed = forwardRef<
.join('|'),
[replyRelayUrls]
)
const stableRelayUrls = useMemo(() => relayUrls, [relayUrlsKey])
const stableReplyRelayUrls = useMemo(() => replyRelayUrls, [replyRelayUrlsKey])
const stableRelayUrls = useMemo(
() => dedupeNormalizeRelayUrlsOrdered(relayUrls),
[relayUrlsKey]
)
const stableReplyRelayUrls = useMemo(
() => dedupeNormalizeRelayUrlsOrdered(replyRelayUrls),
[replyRelayUrlsKey]
)
const homeFeedSeenOnAllowlistOp = useMemo(() => stableRelayUrls, [relayUrlsKey])
const homeFeedSeenOnAllowlistReplies = useMemo(() => stableReplyRelayUrls, [replyRelayUrlsKey])
useEffect(() => {
if (relayUrls.length === 0) {
if (stableRelayUrls.length === 0) {
setAreAlgoRelays(false)
setRelayCapabilityReady(false)
return
}
let cancelled = false
setRelayCapabilityReady(false)
const init = async () => {
const timeoutPromise = new Promise<never>((_, reject) => {
setTimeout(() => {
reject(new Error('getRelayInfos timeout after 8 seconds'))
}, 8000)
})
setRelayCapabilityReady(true)
let cancelled = false
void (async () => {
try {
const relayInfos = await Promise.race([
relayInfoService.getRelayInfos(relayUrls),
timeoutPromise
])
// Memory + IndexedDB cache first; network only for stale/missing (see relay-info.service).
const relayInfos = await relayInfoService.getRelayInfos(stableRelayUrls)
if (cancelled) return
const areAlgo = relayInfos.every((relayInfo) => checkAlgoRelay(relayInfo))
setAreAlgoRelays(areAlgo)
setAreAlgoRelays(relayInfos.every((relayInfo) => checkAlgoRelay(relayInfo)))
} catch {
if (!cancelled) setAreAlgoRelays(false)
} finally {
if (!cancelled) setRelayCapabilityReady(true)
}
}
void init()
})()
return () => {
cancelled = true
}
}, [relayUrlsKey, relayUrls.length])
}, [relayUrlsKey, stableRelayUrls])
/** Stable identity when kind filter is empty so `subRequests` does not invalidate every render. */
const fallbackNoteKinds = useMemo(() => [kinds.ShortTextNote], [])
@ -92,14 +87,14 @@ const RelaysFeed = forwardRef< @@ -92,14 +87,14 @@ const RelaysFeed = forwardRef<
}, [kindsOverride, showKinds, fallbackNoteKinds])
const defaultKindsKey = useMemo(() => JSON.stringify(defaultKinds), [defaultKinds])
const canRenderFeed = relayUrls.length > 0
const canRenderFeed = stableRelayUrls.length > 0
// Hooks must run every render — never place useMemo after conditional returns.
const subRequests = useMemo(() => {
if (!canRenderFeed) return []
return [
{
urls: ensureHomeFeedTrendingRelay(stableRelayUrls),
urls: dedupeNormalizeRelayUrlsOrdered(ensureHomeFeedTrendingRelay(stableRelayUrls)),
filter: {
kinds: defaultKinds
}
@ -112,7 +107,7 @@ const RelaysFeed = forwardRef< @@ -112,7 +107,7 @@ const RelaysFeed = forwardRef<
stableReplyRelayUrls.length > 0 ? stableReplyRelayUrls : stableRelayUrls
return [
{
urls: ensureHomeFeedTrendingRelay(replyUrls),
urls: dedupeNormalizeRelayUrlsOrdered(ensureHomeFeedTrendingRelay(replyUrls)),
filter: {
kinds: defaultKinds
}

11
src/pages/primary/SpellsPage/index.tsx

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
import NoteList, { type TNoteListRef } from '@/components/NoteList'
import { NoteCardLoadingSkeleton } from '@/components/NoteCard'
import StoredAccountSwitchSelect from '@/components/StoredAccountSwitchSelect'
import { RefreshButton } from '@/components/RefreshButton'
import { Button } from '@/components/ui/button'
@ -230,6 +231,7 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage( @@ -230,6 +231,7 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage(
notificationsMentionExtraHide,
hideRepliesFollowing,
fauxSubRequests,
followingFeedPreparing,
NOTIFICATION_SPELL_LOADING_SAFETY_MS,
NOTIFICATION_SPELL_KINDS
} = useSpellsPageFeed({
@ -1046,6 +1048,15 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage( @@ -1046,6 +1048,15 @@ const SpellsPage = forwardRef<TPageRef>(function SpellsPage(
refreshKey={profileInteractionsRefreshKey}
/>
</div>
) : selectedFauxSpell && followingFeedPreparing ? (
<div className="space-y-2 px-1 py-8" role="status" aria-busy="true" aria-live="polite">
<p className="text-center text-sm text-muted-foreground">
{t('Loading recent posts from follows…')}
</p>
{Array.from({ length: 5 }).map((_, i) => (
<NoteCardLoadingSkeleton key={i} />
))}
</div>
) : selectedFauxSpell && fauxSubRequests.length === 0 ? (
<div className="py-8 text-center text-muted-foreground">{fauxFeedEmptyMessage}</div>
) : selectedFauxSpell && fauxSubRequests.length > 0 ? (

12
src/pages/primary/SpellsPage/useSpellsPageFeed.ts

@ -631,11 +631,23 @@ export function useSpellsPageFeed(a: UseSpellsPageFeedArgs) { @@ -631,11 +631,23 @@ export function useSpellsPageFeed(a: UseSpellsPageFeedArgs) {
]
)
const followingFeedPreparing = useMemo(() => {
if (!pubkey || !isFollowFeedFauxSpellId(selectedFauxSpell ?? '')) return false
if (followSetCatalogLoading) return true
return followingSubRequests.length === 0
}, [
pubkey,
selectedFauxSpell,
followSetCatalogLoading,
followingSubRequests.length
])
return {
relayMailboxStableKey,
sortedFavoriteRelaysKey,
sortedBlockedRelaysKey,
followingSubRequests,
followingFeedPreparing,
fauxSubRequests,
subRequests,
spellFeedSubscriptionKey,

4
src/services/relay-info.service.ts

@ -74,8 +74,8 @@ class RelayInfoService { @@ -74,8 +74,8 @@ class RelayInfoService {
if (urls.length === 0) {
return []
}
const relayInfos = await this.fetchDataloader.loadMany(urls)
return relayInfos.map((relayInfo) => (relayInfo instanceof Error ? undefined : relayInfo))
const results = await Promise.allSettled(urls.map((url) => this._getRelayInfo(url)))
return results.map((res) => (res.status === 'fulfilled' ? res.value : undefined))
}
async getRelayInfo(url: string) {

Loading…
Cancel
Save