@ -47,7 +47,8 @@ import {
useLayoutEffect ,
useLayoutEffect ,
useMemo ,
useMemo ,
useRef ,
useRef ,
useState
useState ,
type ReactNode
} from 'react'
} from 'react'
import { CircleAlert } from 'lucide-react'
import { CircleAlert } from 'lucide-react'
import { useLongPressAction } from '@/hooks/use-long-press-action'
import { useLongPressAction } from '@/hooks/use-long-press-action'
@ -254,8 +255,9 @@ const NoteList = forwardRef(
* /
* /
timelineLoadingSafetyTimeoutMs ,
timelineLoadingSafetyTimeoutMs ,
/ * *
/ * *
* With { @link useFilterAsIs } : omit relay ` kinds ` when the subrequest filter has none , and narrow
* With { @link useFilterAsIs } : omit relay ` kinds ` when the subrequest filter has none . Kindless relay feeds
* incoming events to { @link showKinds } before merging ( so caps are not filled by unrelated kinds ) .
* merge the full batch ; the kind picker still applies in the list via { @link applyKindPickerInUi } . Other
* ` useFilterAsIs ` paths may still narrow merged batches to { @link showKinds } .
* /
* /
clientSideKindFilter = false ,
clientSideKindFilter = false ,
/ * *
/ * *
@ -293,7 +295,9 @@ const NoteList = forwardRef(
* When { @link NormalFeed } renders Notes / Replies + kind row , it passes the slot element so the 🔍 control
* When { @link NormalFeed } renders Notes / Replies + kind row , it passes the slot element so the 🔍 control
* sits on that row instead of an extra bar above the list . Omitted on spells / standalone NoteList .
* sits on that row instead of an extra bar above the list . Omitted on spells / standalone NoteList .
* /
* /
feedClientFilterTabRowHost
feedClientFilterTabRowHost ,
onSingleRelayKindlessEmpty ,
feedTopNotice
} : {
} : {
subRequests : TFeedSubRequest [ ]
subRequests : TFeedSubRequest [ ]
showKinds : number [ ]
showKinds : number [ ]
@ -335,6 +339,10 @@ const NoteList = forwardRef(
showFeedClientFilter? : boolean
showFeedClientFilter? : boolean
hostPrimaryPageName? : TPrimaryPageName
hostPrimaryPageName? : TPrimaryPageName
feedClientFilterTabRowHost? : HTMLElement | null
feedClientFilterTabRowHost? : HTMLElement | null
/** Single-relay kindless: if EOSE with no events, parent switches to explicit kinds in `subRequests`. */
onSingleRelayKindlessEmpty ? : ( ) = > void
/** Optional banner above the feed (e.g. kindless→kinds fallback). */
feedTopNotice? : ReactNode
} ,
} ,
ref
ref
) = > {
) = > {
@ -400,6 +408,10 @@ const NoteList = forwardRef(
const feedPaintLiveRelayDoneRef = useRef ( false )
const feedPaintLiveRelayDoneRef = useRef ( false )
/** True if any timeline `onEvents` batch had `batch.length > 0`, or one-shot fetches returned any raw events (before UI filters). */
/** True if any timeline `onEvents` batch had `batch.length > 0`, or one-shot fetches returned any raw events (before UI filters). */
const feedRelayReturnedAnyEventRef = useRef ( false )
const feedRelayReturnedAnyEventRef = useRef ( false )
/** One-shot per timeline init: avoid double-calling parent fallback (Strict Mode / duplicate EOSE). */
const singleRelayKindlessFallbackAttemptedRef = useRef ( false )
const onSingleRelayKindlessEmptyRef = useRef ( onSingleRelayKindlessEmpty )
onSingleRelayKindlessEmptyRef . current = onSingleRelayKindlessEmpty
/** Dedupe {@link toast.error} when relays return nothing for a feed load. */
/** Dedupe {@link toast.error} when relays return nothing for a feed load. */
const emptyRelayNoHitsToastKeyRef = useRef ( '' )
const emptyRelayNoHitsToastKeyRef = useRef ( '' )
/** Per-relay outcomes for the current subscribe wave (merged shards); drives empty-feed toast detail. */
/** Per-relay outcomes for the current subscribe wave (merged shards); drives empty-feed toast detail. */
@ -605,8 +617,9 @@ const NoteList = forwardRef(
clientSideKindFilterRef . current = clientSideKindFilter
clientSideKindFilterRef . current = clientSideKindFilter
/ * *
/ * *
* When to apply kind picker + kind - 1 / 1111 / GitRelease visibility to rows . Kindless home relay chips use a
* When to apply kind picker + kind - 1 / 1111 / GitRelease visibility to visible rows . Kindless relay REQs merge
* kindless REQ and narrow here via { @link clientSideKindFilter } ; standalone relay explore keeps firehose .
* the full relay batch ; this still filters what the list shows ( unlike standalone relay explore , which sets
* { @link allowKindlessRelayExplore } without { @link clientSideKindFilter } and shows the firehose ) .
* /
* /
const applyKindPickerInUi = useMemo (
const applyKindPickerInUi = useMemo (
( ) = >
( ) = >
@ -1184,6 +1197,7 @@ const NoteList = forwardRef(
feedPaintRelayMetaRef . current = null
feedPaintRelayMetaRef . current = null
feedPaintLiveRelayDoneRef . current = false
feedPaintLiveRelayDoneRef . current = false
feedRelayReturnedAnyEventRef . current = false
feedRelayReturnedAnyEventRef . current = false
singleRelayKindlessFallbackAttemptedRef . current = false
// Re-subscribe with rows visible (e.g. relay URL expansion): don't flash global loading / skeleton.
// Re-subscribe with rows visible (e.g. relay URL expansion): don't flash global loading / skeleton.
const keepRowsVisible =
const keepRowsVisible =
@ -1289,18 +1303,16 @@ const NoteList = forwardRef(
return undefined
return undefined
}
}
/ * *
* Kindless relay REQ ( ` allowKindlessRelayExplore ` ) : never drop events here — relays return many kinds ;
* merging only rows in { @link showKinds } left almost nothing in the timeline ( e . g . christpill 200 events → 1
* visible ) while relay explore showed the full firehose . { @link applyKindPickerInUi } / { @link filteredEvents }
* still apply the kind picker for what the user sees .
* /
const narrowLiveBatch = ( evs : Event [ ] ) = > {
const narrowLiveBatch = ( evs : Event [ ] ) = > {
if ( seeAllFeedEventsRef . current ) return evs
if ( seeAllFeedEventsRef . current ) return evs
if (
if ( allowKindlessRelayExploreRef . current ) return evs
allowKindlessRelayExploreRef . current &&
if ( ! useFilterAsIsRef . current || ! clientSideKindFilterRef . current ) return evs
! ( useFilterAsIsRef . current && clientSideKindFilterRef . current )
) {
return evs
}
if ( ! useFilterAsIsRef . current || ! clientSideKindFilterRef . current ) {
if ( ! allowKindlessRelayExploreRef . current ) return evs
return evs
}
return evs . filter ( ( e ) = > showKinds . includes ( e . kind ) )
return evs . filter ( ( e ) = > showKinds . includes ( e . kind ) )
}
}
@ -1536,15 +1548,38 @@ const NoteList = forwardRef(
setHasMore ( true )
setHasMore ( true )
}
}
}
}
// Single-relay home chip: kindless REQ returned nothing — parent re-subscribes with explicit kinds.
if (
eosed &&
effectActive &&
onSingleRelayKindlessEmptyRef . current &&
! singleRelayKindlessFallbackAttemptedRef . current &&
! feedRelayReturnedAnyEventRef . current
) {
const reqs = subRequestsRef . current
const f0 = reqs [ 0 ]
if (
reqs . length === 1 &&
f0 &&
f0 . urls . length === 1 &&
allowKindlessRelayExploreRef . current &&
useFilterAsIsRef . current &&
clientSideKindFilterRef . current
) {
const f = f0 . filter as Filter
const noKinds = ! f . kinds || f . kinds . length === 0
if ( noKinds ) {
singleRelayKindlessFallbackAttemptedRef . current = true
onSingleRelayKindlessEmptyRef . current ( )
}
}
}
} ,
} ,
onNew : ( event : Event ) = > {
onNew : ( event : Event ) = > {
if ( ! effectActive ) return
if ( ! effectActive ) return
feedRelayReturnedAnyEventRef . current = true
feedRelayReturnedAnyEventRef . current = true
if (
if ( ! seeAllFeedEventsRef . current && ! allowKindlessRelayExploreRef . current ) {
! seeAllFeedEventsRef . current &&
( ! allowKindlessRelayExploreRef . current ||
( useFilterAsIsRef . current && clientSideKindFilterRef . current ) )
) {
if ( ! useFilterAsIsRef . current && ! showKinds . includes ( event . kind ) ) return
if ( ! useFilterAsIsRef . current && ! showKinds . includes ( event . kind ) ) return
if (
if (
clientSideKindFilterRef . current &&
clientSideKindFilterRef . current &&
@ -1657,7 +1692,8 @@ const NoteList = forwardRef(
oneShotEoseTimeoutMs ,
oneShotEoseTimeoutMs ,
oneShotFirstRelayGraceMs ,
oneShotFirstRelayGraceMs ,
clientSideKindFilter ,
clientSideKindFilter ,
allowKindlessRelayExplore
allowKindlessRelayExplore ,
onSingleRelayKindlessEmpty
] )
] )
const oneShotDebugPrevLoadingRef = useRef ( false )
const oneShotDebugPrevLoadingRef = useRef ( false )
@ -2461,12 +2497,28 @@ const NoteList = forwardRef(
pullingContent = ""
pullingContent = ""
>
>
< div >
< div >
{ feedTopNotice ? (
< div
className = "mb-2 rounded-md border border-border/80 bg-muted/35 px-3 py-2 text-sm text-muted-foreground"
role = "note"
>
{ feedTopNotice }
< / div >
) : null }
{ showFeedClientFilter ? feedClientFilterBar : null }
{ showFeedClientFilter ? feedClientFilterBar : null }
{ list }
{ list }
< / div >
< / div >
< / PullToRefresh >
< / PullToRefresh >
) : (
) : (
< div >
< div >
{ feedTopNotice ? (
< div
className = "mb-2 rounded-md border border-border/80 bg-muted/35 px-3 py-2 text-sm text-muted-foreground"
role = "note"
>
{ feedTopNotice }
< / div >
) : null }
{ showFeedClientFilter ? feedClientFilterBar : null }
{ showFeedClientFilter ? feedClientFilterBar : null }
{ list }
{ list }
< / div >
< / div >