Browse Source

bug-fixes

imwald
Silberengel 1 month ago
parent
commit
eaace9e275
  1. 40
      src/components/NormalFeed/index.tsx
  2. 9
      src/hooks/useNotificationReactionDisplay.ts
  3. 30
      src/lib/home-feed-relays.ts
  4. 3
      src/lib/nostr-land-relay-eligibility.ts
  5. 11
      src/pages/primary/NoteListPage/RelaysFeed.tsx
  6. 24
      src/providers/FeedProvider.test.ts
  7. 57
      src/providers/FeedProvider.tsx
  8. 7
      src/providers/feed-context.tsx

40
src/components/NormalFeed/index.tsx

@ -61,6 +61,11 @@ const NormalFeed = forwardRef<TNoteListRef, { @@ -61,6 +61,11 @@ const NormalFeed = forwardRef<TNoteListRef, {
mergeTimelineWhenSubRequestFiltersMatch?: boolean
/** Home Replies can widen relays without changing Notes/Gallery. */
repliesSubRequests?: TFeedSubRequest[]
/**
* When set on the home main feed, Gallery tab REQ uses this relay stack (same as {@link repliesSubRequests})
* instead of OP-only {@link subRequests} URLs.
*/
mainFeedGalleryRelayUrls?: string[]
/** Main Gallery historically widened with fast read relays; home can opt out to stay favorites+trending only. */
widenMainGalleryRelays?: boolean
/** Home following: second subscribe wave (delta relays / new authors); see {@link NoteList}. */
@ -102,6 +107,8 @@ const NormalFeed = forwardRef<TNoteListRef, { @@ -102,6 +107,8 @@ const NormalFeed = forwardRef<TNoteListRef, {
oneShotAfterMergeComparator?: (a: Event, b: Event) => number
extraShouldHideEvent?: (ev: Event) => boolean
extraShouldHideRepliesEvent?: (ev: Event) => boolean
/** When set with home Gallery, filters rows (e.g. aggr-only) using the widened relay stack. */
extraShouldHideGalleryEvent?: (ev: Event) => boolean
/** Override default cap for merged one-shot batches (wide d-tag / search merges). */
oneShotMergedCap?: number
/** When every relay in the subscribe wave fails before EOSE, merge a one-shot fetch from default read relays (home multi-relay feeds). */
@ -119,6 +126,7 @@ const NormalFeed = forwardRef<TNoteListRef, { @@ -119,6 +126,7 @@ const NormalFeed = forwardRef<TNoteListRef, {
preserveTimelineOnSubRequestsChange = false,
mergeTimelineWhenSubRequestFiltersMatch = false,
repliesSubRequests,
mainFeedGalleryRelayUrls,
widenMainGalleryRelays = true,
followingFeedDeltaSubRequests,
feedSubscriptionKey,
@ -139,6 +147,7 @@ const NormalFeed = forwardRef<TNoteListRef, { @@ -139,6 +147,7 @@ const NormalFeed = forwardRef<TNoteListRef, {
oneShotAfterMergeComparator,
extraShouldHideEvent,
extraShouldHideRepliesEvent,
extraShouldHideGalleryEvent,
oneShotMergedCap,
timelinePublicReadFallback = false,
alexandriaEmptyUrl = null
@ -198,7 +207,7 @@ const NormalFeed = forwardRef<TNoteListRef, { @@ -198,7 +207,7 @@ const NormalFeed = forwardRef<TNoteListRef, {
return base
}, [isMainFeed, isWispTrendingOnlyFeed])
/** Replies may widen relays; Gallery only swaps kinds and widens relays when the caller opts in. */
/** Replies may widen relays; Gallery swaps kinds and may use {@link mainFeedGalleryRelayUrls} on home. */
const effectiveSubRequests = useMemo(() => {
if (listMode === 'postsAndReplies' && repliesSubRequests) {
return repliesSubRequests
@ -206,10 +215,29 @@ const NormalFeed = forwardRef<TNoteListRef, { @@ -206,10 +215,29 @@ const NormalFeed = forwardRef<TNoteListRef, {
if (listMode !== 'media') return subRequests
return subRequests.map((req) => ({
...req,
urls: isMainFeed && widenMainGalleryRelays ? galleryRelayUrlsMergedWithReadLayer(req.urls) : req.urls,
urls:
isMainFeed && mainFeedGalleryRelayUrls && mainFeedGalleryRelayUrls.length > 0
? mainFeedGalleryRelayUrls
: isMainFeed && widenMainGalleryRelays
? galleryRelayUrlsMergedWithReadLayer(req.urls)
: req.urls,
filter: { ...req.filter, kinds: MEDIA_KINDS }
}))
}, [listMode, subRequests, repliesSubRequests, MEDIA_KINDS, isMainFeed, widenMainGalleryRelays])
}, [
listMode,
subRequests,
repliesSubRequests,
MEDIA_KINDS,
isMainFeed,
widenMainGalleryRelays,
mainFeedGalleryRelayUrls
])
const noteListExtraShouldHide = useMemo(() => {
if (listMode === 'postsAndReplies') return extraShouldHideRepliesEvent
if (listMode === 'media' && extraShouldHideGalleryEvent) return extraShouldHideGalleryEvent
return extraShouldHideEvent
}, [listMode, extraShouldHideRepliesEvent, extraShouldHideGalleryEvent, extraShouldHideEvent])
const handleListModeChange = useCallback(
(mode: TNoteListMode | string) => {
@ -374,11 +402,7 @@ const NormalFeed = forwardRef<TNoteListRef, { @@ -374,11 +402,7 @@ const NormalFeed = forwardRef<TNoteListRef, {
progressiveWarmupMatch={progressiveWarmupMatch}
progressiveDocumentKinds={progressiveDocumentKinds}
oneShotAfterMergeComparator={oneShotAfterMergeComparator}
extraShouldHideEvent={
listMode === 'postsAndReplies'
? extraShouldHideRepliesEvent
: extraShouldHideEvent
}
extraShouldHideEvent={noteListExtraShouldHide}
oneShotMergedCap={oneShotMergedCap}
timelinePublicReadFallback={timelinePublicReadFallback && listMode === 'postsAndReplies'}
alexandriaEmptyUrl={alexandriaEmptyUrl}

9
src/hooks/useNotificationReactionDisplay.ts

@ -7,7 +7,6 @@ import { getRootEventHexId } from '@/lib/event' @@ -7,7 +7,6 @@ import { getRootEventHexId } from '@/lib/event'
import { relayHintsFromEventTags } from '@/lib/relay-list-builder'
import { getFirstHexEventIdFromETags } from '@/lib/tag'
import { eventService } from '@/services/client.service'
import type { NEvent } from '@/types'
import { Event, kinds } from 'nostr-tools'
import { useEffect, useLayoutEffect, useMemo, useState } from 'react'
@ -19,8 +18,8 @@ export type NotificationReactionDisplay = @@ -19,8 +18,8 @@ export type NotificationReactionDisplay =
function classifyDiscussionReactionFromTargets(
reaction: Event,
target: NEvent,
root: NEvent | undefined
target: Event,
root: Event | undefined
): NotificationReactionDisplay {
let inDiscussion = target.kind === ExtendedKind.DISCUSSION
if (!inDiscussion && target.kind === ExtendedKind.COMMENT) {
@ -39,7 +38,7 @@ function peekReactionDisplayFromSessionCaches(event: Event): NotificationReactio @@ -39,7 +38,7 @@ function peekReactionDisplayFromSessionCaches(event: Event): NotificationReactio
if (!targetId) return { status: 'default' }
const target = eventService.peekHexIdNoteFromSessionCache(targetId)
if (!target) return { status: 'default' }
let root: NEvent | undefined
let root: Event | undefined
if (target.kind === ExtendedKind.COMMENT) {
const rootId = getRootEventHexId(target)
if (rootId) root = eventService.peekHexIdNoteFromSessionCache(rootId)
@ -93,7 +92,7 @@ export function useNotificationReactionDisplay(event: Event): NotificationReacti @@ -93,7 +92,7 @@ export function useNotificationReactionDisplay(event: Event): NotificationReacti
return
}
let root: NEvent | undefined
let root: Event | undefined
let inDiscussion = target.kind === ExtendedKind.DISCUSSION
if (!inDiscussion && target.kind === ExtendedKind.COMMENT) {
const rootId = getRootEventHexId(target)

30
src/lib/home-feed-relays.ts

@ -9,19 +9,29 @@ function relayUrlIsNostrLandAggr(url: string): boolean { @@ -9,19 +9,29 @@ function relayUrlIsNostrLandAggr(url: string): boolean {
return normalized === aggr
}
/** Drop nostr.land aggregate from REQ stacks where it must not appear (e.g. home feeds). */
export function stripNostrLandAggrFromRelayUrls(urls: readonly string[]): string[] {
return urls.filter((url) => !relayUrlIsNostrLandAggr(url))
}
export function buildAllFavoritesFeedRelayUrls(
favoriteRelays: string[],
blockedRelays: string[],
extraFeedRelayUrls: string[]
): string[] {
return feedRelayPolicyUrls([
{ source: 'favorites', urls: getFavoritesFeedRelayUrls(favoriteRelays, blockedRelays) },
{ source: 'fallback', urls: extraFeedRelayUrls }
], {
operation: 'favorites-feed',
blockedRelays,
nostrLandAggr: 'never',
applySocialKindBlockedFilter: false,
allowThirdPartyLocalRelays: true
}).filter((url) => !relayUrlIsNostrLandAggr(url))
return stripNostrLandAggrFromRelayUrls(
feedRelayPolicyUrls(
[
{ source: 'favorites', urls: getFavoritesFeedRelayUrls(favoriteRelays, blockedRelays) },
{ source: 'fallback', urls: extraFeedRelayUrls }
],
{
operation: 'favorites-feed',
blockedRelays,
nostrLandAggr: 'never',
applySocialKindBlockedFilter: false,
allowThirdPartyLocalRelays: true
}
)
)
}

3
src/lib/nostr-land-relay-eligibility.ts

@ -3,7 +3,8 @@ import { normalizeAnyRelayUrl } from '@/lib/url' @@ -3,7 +3,8 @@ import { normalizeAnyRelayUrl } from '@/lib/url'
/**
* True when any URLs host is `nostr.land` (e.g. `wss://nostr.land`, `wss://aggr.nostr.land`).
* Used to decide whether read fetches should prepend {@link AGGR_NOSTR_LAND_WSS} (except the primary home OP feed).
* Used to decide whether read fetches should prepend {@link AGGR_NOSTR_LAND_WSS} (home OP / Replies / Gallery
* never prepend aggr via {@link FeedProvider}; side-panel threads, profiles, spells, and other reads still use it).
*/
export function relayUrlsMentionNostrLandDomain(urls: readonly string[]): boolean {
return urls.some((url) => {

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

@ -115,6 +115,15 @@ const RelaysFeed = forwardRef< @@ -115,6 +115,15 @@ const RelaysFeed = forwardRef<
},
[relayUrls]
)
const hideAggrOnlyReplyGalleryStackEvent = useCallback(
(event: Event) => {
const seenRelays = client.getSeenEventRelayUrls(event.id).map(relaySeenKey)
if (!seenRelays.includes(AGGR_RELAY_KEY)) return false
const allowedRelays = new Set(replyRelayUrls.map(relaySeenKey))
return !seenRelays.some((relay) => relay !== AGGR_RELAY_KEY && allowedRelays.has(relay))
},
[replyRelayUrls]
)
const hideAggrOnlyNonReplyEvent = useCallback(
(event: Event) => hideAggrOnlyMainFeedEvent(event) && !isReplyNoteEvent(event),
[hideAggrOnlyMainFeedEvent]
@ -135,12 +144,14 @@ const RelaysFeed = forwardRef< @@ -135,12 +144,14 @@ const RelaysFeed = forwardRef<
onSubHeaderRefresh={onSubHeaderRefresh}
preserveTimelineOnSubRequestsChange
repliesSubRequests={repliesSubRequests}
mainFeedGalleryRelayUrls={replyRelayUrls}
widenMainGalleryRelays={false}
feedSubscriptionKey="home-all-favorites"
feedTimelineScopeKey="all-favorites"
showFeedClientFilter
hostPrimaryPageName="feed"
extraShouldHideEvent={hideAggrOnlyMainFeedEvent}
extraShouldHideGalleryEvent={hideAggrOnlyReplyGalleryStackEvent}
extraShouldHideRepliesEvent={hideAggrOnlyNonReplyEvent}
timelinePublicReadFallback
/>

24
src/providers/FeedProvider.test.ts

@ -1,7 +1,8 @@ @@ -1,7 +1,8 @@
import { describe, expect, it } from 'vitest'
import { feedRelayPolicyUrls } from '@/features/feed/relay-policy'
import { AGGR_NOSTR_LAND_WSS } from '@/lib/nostr-land-aggr'
import { buildWispTrendingNotesRelayUrl } from '@/lib/wisp-trending-relay'
import { buildAllFavoritesFeedRelayUrls } from '@/lib/home-feed-relays'
import { buildAllFavoritesFeedRelayUrls, stripNostrLandAggrFromRelayUrls } from '@/lib/home-feed-relays'
describe('home feed relay policy', () => {
it('keeps aggr.nostr.land out of the main home feed', () => {
@ -16,4 +17,25 @@ describe('home feed relay policy', () => { @@ -16,4 +17,25 @@ describe('home feed relay policy', () => {
expect(urls).not.toContain('wss://aggr.nostr.land/')
expect(urls).not.toContain(AGGR_NOSTR_LAND_WSS)
})
it('home reply merge omits aggr even when listed on viewer read relays', () => {
const merged = stripNostrLandAggrFromRelayUrls(
feedRelayPolicyUrls(
[
{ source: 'favorites', urls: ['wss://relay.example/'] },
{ source: 'viewer-read', urls: [AGGR_NOSTR_LAND_WSS, 'wss://inbox.example/'] }
],
{
operation: 'read',
blockedRelays: [],
nostrLandAggr: 'never',
applySocialKindBlockedFilter: false,
allowThirdPartyLocalRelays: true
}
)
)
expect(merged).not.toContain(AGGR_NOSTR_LAND_WSS)
expect(merged).toContain('wss://relay.example/')
expect(merged).toContain('wss://inbox.example/')
})
})

57
src/providers/FeedProvider.tsx

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import { FAST_READ_RELAY_URLS } from '@/constants'
import { feedRelayPolicyUrls } from '@/features/feed/relay-policy'
import { getRelayListFromEvent, getHttpRelayListFromEvent } from '@/lib/event-metadata'
import { buildAllFavoritesFeedRelayUrls } from '@/lib/home-feed-relays'
import { buildAllFavoritesFeedRelayUrls, stripNostrLandAggrFromRelayUrls } from '@/lib/home-feed-relays'
import logger from '@/lib/logger'
import { syncViewerRelayStackNostrLandAggrEligible } from '@/lib/nostr-land-relay-eligibility'
import { normalizeAnyRelayUrl } from '@/lib/url'
@ -29,19 +29,23 @@ function buildHomeReplyFeedRelayUrls( @@ -29,19 +29,23 @@ function buildHomeReplyFeedRelayUrls(
httpRelayUrls: string[],
blockedRelays: string[]
): string[] {
return feedRelayPolicyUrls(
[
{ source: 'favorites', urls: primaryRelayUrls },
{ source: 'viewer-read', urls: inboxRelayUrls },
{ source: 'cache', urls: cacheRelayUrls },
{ source: 'http-index', urls: httpRelayUrls }
],
{
operation: 'read',
blockedRelays,
applySocialKindBlockedFilter: false,
allowThirdPartyLocalRelays: true
}
/** Home Replies/Gallery: never prepend aggr (reserved for side-panel threads, profiles, spells). */
return stripNostrLandAggrFromRelayUrls(
feedRelayPolicyUrls(
[
{ source: 'favorites', urls: primaryRelayUrls },
{ source: 'viewer-read', urls: inboxRelayUrls },
{ source: 'cache', urls: cacheRelayUrls },
{ source: 'http-index', urls: httpRelayUrls }
],
{
operation: 'read',
blockedRelays,
nostrLandAggr: 'never',
applySocialKindBlockedFilter: false,
allowThirdPartyLocalRelays: true
}
)
)
}
@ -60,18 +64,18 @@ export function FeedProvider({ children }: { children: ReactNode }) { @@ -60,18 +64,18 @@ export function FeedProvider({ children }: { children: ReactNode }) {
*/
const primaryExtraRelayUrls = useMemo(() => [buildWispTrendingNotesRelayUrl()], [])
/** Home Replies widen to relays that can surface inbox/reply context. */
/** Read-side layers merged into {@link replyRelayUrls}; {@link outboxRelayUrls} is only for aggr eligibility sync. */
const replyExtraRelayLayers = useMemo(() => {
const cacheRelayUrls: string[] = []
if (cacheRelayListEvent) {
const list = getRelayListFromEvent(cacheRelayListEvent, blockedRelays)
cacheRelayUrls.push(...list.read, ...list.write)
cacheRelayUrls.push(...list.read)
}
const httpRelayUrls: string[] = [...(relayList?.httpRead ?? []), ...(relayList?.httpWrite ?? [])]
const httpRelayUrls: string[] = [...(relayList?.httpRead ?? [])]
if (httpRelayListEvent) {
const list = getHttpRelayListFromEvent(httpRelayListEvent, blockedRelays)
httpRelayUrls.push(...list.httpRead, ...list.httpWrite)
httpRelayUrls.push(...list.httpRead)
}
return {
@ -106,7 +110,8 @@ export function FeedProvider({ children }: { children: ReactNode }) { @@ -106,7 +110,8 @@ export function FeedProvider({ children }: { children: ReactNode }) {
[]
)
const viewerNostrLandAggrEligible = useMemo(() => {
/** Keeps {@link getViewerRelayStackNostrLandAggrEligible} in sync for non-home reads (threads, profiles, etc.). */
useEffect(() => {
const urls = [
...favoriteFeedRelayUrls,
...replyExtraRelayLayers.inboxRelayUrls,
@ -114,7 +119,7 @@ export function FeedProvider({ children }: { children: ReactNode }) { @@ -114,7 +119,7 @@ export function FeedProvider({ children }: { children: ReactNode }) {
...replyExtraRelayLayers.cacheRelayUrls,
...replyExtraRelayLayers.httpRelayUrls
]
return syncViewerRelayStackNostrLandAggrEligible(urls)
syncViewerRelayStackNostrLandAggrEligible(urls)
}, [favoriteFeedRelayUrls, replyExtraRelayLayers])
const lastHomeFeedUrlLogRef = useRef({ primary: '', reply: '' })
@ -139,14 +144,7 @@ export function FeedProvider({ children }: { children: ReactNode }) { @@ -139,14 +144,7 @@ export function FeedProvider({ children }: { children: ReactNode }) {
}
setUrlStateIfChanged(setRelayUrls, primaryRelays)
setUrlStateIfChanged(setReplyRelayUrls, replyRelays)
}, [
favoriteFeedRelayUrls,
blockedRelays,
primaryExtraRelayUrls,
replyExtraRelayLayers,
setUrlStateIfChanged,
viewerNostrLandAggrEligible
])
}, [favoriteFeedRelayUrls, blockedRelays, primaryExtraRelayUrls, replyExtraRelayLayers, setUrlStateIfChanged])
const favoriteRelaysIdentity = useMemo(
() =>
@ -170,7 +168,6 @@ export function FeedProvider({ children }: { children: ReactNode }) { @@ -170,7 +168,6 @@ export function FeedProvider({ children }: { children: ReactNode }) {
() =>
[
...replyExtraRelayLayers.inboxRelayUrls,
...replyExtraRelayLayers.outboxRelayUrls,
...replyExtraRelayLayers.cacheRelayUrls,
...replyExtraRelayLayers.httpRelayUrls
]
@ -191,7 +188,6 @@ export function FeedProvider({ children }: { children: ReactNode }) { @@ -191,7 +188,6 @@ export function FeedProvider({ children }: { children: ReactNode }) {
relaySets.length,
favoriteFeedRelayUrls.length - favoriteRelays.length,
replyExtraRelayLayers.inboxRelayUrls.length,
replyExtraRelayLayers.outboxRelayUrls.length,
replyExtraRelayLayers.cacheRelayUrls.length,
replyExtraRelayLayers.httpRelayUrls.length,
blockedRelays.length
@ -206,7 +202,6 @@ export function FeedProvider({ children }: { children: ReactNode }) { @@ -206,7 +202,6 @@ export function FeedProvider({ children }: { children: ReactNode }) {
relaySets: relaySets.length,
relaySetRelays: favoriteFeedRelayUrls.length - favoriteRelays.length,
inboxRelays: replyExtraRelayLayers.inboxRelayUrls.length,
outboxRelays: replyExtraRelayLayers.outboxRelayUrls.length,
cacheRelays: replyExtraRelayLayers.cacheRelayUrls.length,
httpRelays: replyExtraRelayLayers.httpRelayUrls.length,
blockedRelays: blockedRelays.length

7
src/providers/feed-context.tsx

@ -5,9 +5,12 @@ @@ -5,9 +5,12 @@
import { createContext, useContext } from 'react'
export type TFeedContext = {
/** Home Notes/Gallery: favorites plus mixed trending discovery. */
/** Home Notes (OP): favorites plus Wisp trending only — no aggr, no FAST_READ padding. */
relayUrls: string[]
/** Home Replies: primary feed relays plus viewer inbox, HTTP, cache, and eligible aggregator relays. */
/**
* Home Replies + Gallery: same OP base, then NIP-65 read inboxes (FAST_READ when empty), kind 10432 cache
* read relays, HTTP read index never aggr (aggr is for side-panel threads, profiles, and spells only).
*/
replyRelayUrls: string[]
}

Loading…
Cancel
Save