Browse Source

bug-fixes

imwald
Silberengel 1 month ago
parent
commit
30e7cde493
  1. 15
      src/components/KindFilter/index.tsx
  2. 41
      src/components/NormalFeed/index.tsx
  3. 82
      src/components/Note/UnknownNote.tsx
  4. 89
      src/components/NoteList/index.tsx
  5. 2
      src/components/Relay/index.tsx
  6. 1
      src/i18n/locales/de.ts
  7. 1
      src/i18n/locales/en.ts
  8. 11
      src/pages/primary/NoteListPage/RelaysFeed.tsx

15
src/components/KindFilter/index.tsx

@ -308,11 +308,13 @@ export default function KindFilter({
{trigger} {trigger}
<Drawer open={open} onOpenChange={setOpen}> <Drawer open={open} onOpenChange={setOpen}>
<DrawerTrigger asChild></DrawerTrigger> <DrawerTrigger asChild></DrawerTrigger>
<DrawerContent className="px-4"> <DrawerContent className="flex max-h-[90dvh] flex-col px-4 min-h-0">
<DrawerHeader className="sr-only"> <DrawerHeader className="sr-only">
<DrawerTitle>Filter</DrawerTitle> <DrawerTitle>Filter</DrawerTitle>
</DrawerHeader> </DrawerHeader>
<div className="min-h-0 flex-1 overflow-y-auto overscroll-contain pb-4">
{content} {content}
</div>
</DrawerContent> </DrawerContent>
</Drawer> </Drawer>
</> </>
@ -322,8 +324,15 @@ export default function KindFilter({
return ( return (
<Popover open={open} onOpenChange={setOpen}> <Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>{trigger}</PopoverTrigger> <PopoverTrigger asChild>{trigger}</PopoverTrigger>
<PopoverContent className="w-96" collisionPadding={16} sideOffset={0}> <PopoverContent
{content} className="flex w-96 max-h-[min(85dvh,calc(100dvh-6rem))] flex-col gap-0 overflow-hidden p-0"
collisionPadding={{ top: 80, bottom: 20, left: 16, right: 16 }}
side="bottom"
align="end"
sideOffset={6}
sticky="always"
>
<div className="min-h-0 flex-1 overflow-y-auto overscroll-contain p-4">{content}</div>
</PopoverContent> </PopoverContent>
</Popover> </Popover>
) )

41
src/components/NormalFeed/index.tsx

@ -26,6 +26,10 @@ const NormalFeed = forwardRef<TNoteListRef, {
mergeTimelineWhenSubRequestFiltersMatch?: boolean mergeTimelineWhenSubRequestFiltersMatch?: boolean
/** Home favorite-relays chip scope; see {@link NoteList} `feedTimelineScopeKey`. */ /** Home favorite-relays chip scope; see {@link NoteList} `feedTimelineScopeKey`. */
feedTimelineScopeKey?: string feedTimelineScopeKey?: string
/** Single-relay Explore / chip: kindless REQ (limit 200), no feed kind filter. */
useFilterAsIs?: boolean
clientSideKindFilter?: boolean
allowKindlessRelayExplore?: boolean
}>(function NormalFeed( }>(function NormalFeed(
{ {
subRequests, subRequests,
@ -36,7 +40,10 @@ const NormalFeed = forwardRef<TNoteListRef, {
onSubHeaderRefresh, onSubHeaderRefresh,
preserveTimelineOnSubRequestsChange = false, preserveTimelineOnSubRequestsChange = false,
mergeTimelineWhenSubRequestFiltersMatch = false, mergeTimelineWhenSubRequestFiltersMatch = false,
feedTimelineScopeKey feedTimelineScopeKey,
useFilterAsIs = false,
clientSideKindFilter = false,
allowKindlessRelayExplore = false
}, },
ref ref
) { ) {
@ -84,6 +91,10 @@ const NormalFeed = forwardRef<TNoteListRef, {
const showKindsKey = useMemo(() => JSON.stringify(showKinds), [showKinds]) const showKindsKey = useMemo(() => JSON.stringify(showKinds), [showKinds])
const subHeaderFilterDepsKey = allowKindlessRelayExplore
? 'kindless-relay-explore'
: `${showKindsKey}|${feedKindFilterBypass}`
const tabsElement = ( const tabsElement = (
<Tabs <Tabs
value={listMode} value={listMode}
@ -92,7 +103,9 @@ const NormalFeed = forwardRef<TNoteListRef, {
options={ options={
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
{onSubHeaderRefresh != null && <RefreshButton onClick={onSubHeaderRefresh} />} {onSubHeaderRefresh != null && <RefreshButton onClick={onSubHeaderRefresh} />}
{!allowKindlessRelayExplore && (
<KindFilter showKinds={showKinds} onShowKindsChange={handleShowKindsChange} /> <KindFilter showKinds={showKinds} onShowKindsChange={handleShowKindsChange} />
)}
</div> </div>
} }
/> />
@ -100,11 +113,28 @@ const NormalFeed = forwardRef<TNoteListRef, {
useLayoutEffect(() => { useLayoutEffect(() => {
if (!isMainFeed || !setSubHeader) return if (!isMainFeed || !setSubHeader) return
if (allowKindlessRelayExplore) {
setSubHeader(
onSubHeaderRefresh != null ? (
<div className="flex w-full items-center justify-end gap-1">
<RefreshButton onClick={onSubHeaderRefresh} />
</div>
) : null
)
return () => setSubHeader(null)
}
setSubHeader(tabsElement) setSubHeader(tabsElement)
return () => setSubHeader(null) return () => setSubHeader(null)
}, [isMainFeed, setSubHeader, listMode, showKindsKey, feedKindFilterBypass, onSubHeaderRefresh]) }, [
isMainFeed,
setSubHeader,
listMode,
subHeaderFilterDepsKey,
onSubHeaderRefresh,
allowKindlessRelayExplore
])
const renderTabsInFeed = !(isMainFeed && setSubHeader) const renderTabsInFeed = !(isMainFeed && setSubHeader) && !allowKindlessRelayExplore
return ( return (
<> <>
@ -118,13 +148,16 @@ const NormalFeed = forwardRef<TNoteListRef, {
showKind1111={showKind1111} showKind1111={showKind1111}
seeAllFeedEvents={feedKindFilterBypass} seeAllFeedEvents={feedKindFilterBypass}
subRequests={subRequests} subRequests={subRequests}
hideReplies={listMode === 'posts'} hideReplies={allowKindlessRelayExplore ? false : listMode === 'posts'}
hideUntrustedNotes={hideUntrustedNotes} hideUntrustedNotes={hideUntrustedNotes}
areAlgoRelays={areAlgoRelays} areAlgoRelays={areAlgoRelays}
relayCapabilityReady={relayCapabilityReady} relayCapabilityReady={relayCapabilityReady}
preserveTimelineOnSubRequestsChange={preserveTimelineOnSubRequestsChange} preserveTimelineOnSubRequestsChange={preserveTimelineOnSubRequestsChange}
mergeTimelineWhenSubRequestFiltersMatch={mergeTimelineWhenSubRequestFiltersMatch} mergeTimelineWhenSubRequestFiltersMatch={mergeTimelineWhenSubRequestFiltersMatch}
feedTimelineScopeKey={feedTimelineScopeKey} feedTimelineScopeKey={feedTimelineScopeKey}
useFilterAsIs={useFilterAsIs}
clientSideKindFilter={clientSideKindFilter}
allowKindlessRelayExplore={allowKindlessRelayExplore}
/> />
</div> </div>
</> </>

82
src/components/Note/UnknownNote.tsx

@ -34,6 +34,9 @@ const ELEVATED_TAG_NAMES = new Set([
'pubkey' 'pubkey'
]) ])
/** e / p / q / a: thread & pubkey refs — noisy in preview; show under Technical details only. */
const TECHNICAL_ONLY_TAG_NAMES = new Set(['e', 'p', 'q', 'a'])
function truncatePreview(text: string, max: number): string { function truncatePreview(text: string, max: number): string {
const t = text.trim() const t = text.trim()
if (t.length <= max) return t if (t.length <= max) return t
@ -166,7 +169,17 @@ export default function UnknownNote({
const elevated = useMemo(() => extractElevatedTags(event.tags), [event.tags]) const elevated = useMemo(() => extractElevatedTags(event.tags), [event.tags])
const remainderTags = useMemo( const remainderTags = useMemo(
() => event.tags.filter(tag => tag[0] && !ELEVATED_TAG_NAMES.has(tag[0])), () => event.tags.filter((tag) => tag[0] && !ELEVATED_TAG_NAMES.has(tag[0])),
[event.tags]
)
const mainCardTags = useMemo(
() => remainderTags.filter((tag) => !TECHNICAL_ONLY_TAG_NAMES.has(tag[0])),
[remainderTags]
)
const technicalReferenceTags = useMemo(
() => event.tags.filter((tag) => tag[0] && TECHNICAL_ONLY_TAG_NAMES.has(tag[0])),
[event.tags] [event.tags]
) )
@ -199,22 +212,23 @@ export default function UnknownNote({
const showNoTextPlaceholder = const showNoTextPlaceholder =
!contentRaw && !hasAnyElevatedCopy && !isBookstrEvent !contentRaw && !hasAnyElevatedCopy && !isBookstrEvent
const proseClass = 'text-sm leading-relaxed whitespace-pre-wrap break-words text-foreground/95' const proseClass =
'text-xs leading-snug whitespace-pre-wrap break-words text-foreground/95'
return ( return (
<div <div
className={cn( className={cn(
'flex flex-col gap-3 my-4', 'flex flex-col gap-2 my-2',
className className
)} )}
> >
<div className="rounded-lg border border-border bg-card px-4 py-3 text-card-foreground shadow-sm space-y-3"> <div className="rounded-lg border border-border bg-card px-3 py-2 text-card-foreground shadow-sm space-y-2">
<p className="text-sm text-muted-foreground leading-snug"> <p className="text-xs text-muted-foreground leading-snug">
{t('Unsupported event preview')} {t('Unsupported event preview')}
</p> </p>
{showAuthorSummary && isValidPubkey(event.pubkey) ? ( {showAuthorSummary && isValidPubkey(event.pubkey) ? (
<div className="flex min-w-0 items-center gap-2 border-b border-border/60 pb-3"> <div className="flex min-w-0 items-center gap-2 border-b border-border/60 pb-2">
<UserAvatar userId={event.pubkey} size="medium" className="shrink-0" /> <UserAvatar userId={event.pubkey} size="medium" className="shrink-0" />
<Username <Username
userId={event.pubkey} userId={event.pubkey}
@ -225,12 +239,12 @@ export default function UnknownNote({
) : null} ) : null}
<div> <div>
<h3 className="text-base font-semibold leading-tight text-foreground">{headline}</h3> <h3 className="text-sm font-semibold leading-tight text-foreground">{headline}</h3>
{!omitKindLabel ? ( {!omitKindLabel ? (
<NoteKindLabel kind={event.kind} event={event} size="small" className="mt-1" /> <NoteKindLabel kind={event.kind} event={event} size="small" className="mt-0.5" />
) : null} ) : null}
{elevated.title?.trim() && !omitKindLabel ? ( {elevated.title?.trim() && !omitKindLabel ? (
<p className="mt-0.5 text-xs text-muted-foreground"> <p className="mt-0.5 text-[11px] text-muted-foreground leading-snug">
<span className="text-foreground/80">{kindLabel.description}</span> <span className="text-foreground/80">{kindLabel.description}</span>
<span className="mx-1.5 text-border">·</span> <span className="mx-1.5 text-border">·</span>
<span className="font-mono tabular-nums">{t('Event kind label', { kind: event.kind })}</span> <span className="font-mono tabular-nums">{t('Event kind label', { kind: event.kind })}</span>
@ -252,12 +266,12 @@ export default function UnknownNote({
{elevated.topics.length > 0 ? ( {elevated.topics.length > 0 ? (
<div> <div>
<p className="text-xs font-medium uppercase tracking-wide text-muted-foreground mb-2"> <p className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground mb-1">
{t('Topics')} {t('Topics')}
</p> </p>
<div className="flex flex-wrap gap-1.5"> <div className="flex flex-wrap gap-1">
{elevated.topics.map((topic, i) => ( {elevated.topics.map((topic, i) => (
<Badge key={`${topic}-${i}`} variant="secondary" className="font-normal"> <Badge key={`${topic}-${i}`} variant="secondary" className="font-normal text-xs py-0">
{topic} {topic}
</Badge> </Badge>
))} ))}
@ -272,7 +286,7 @@ export default function UnknownNote({
key={`${url}-${i}`} key={`${url}-${i}`}
src={url} src={url}
alt="" alt=""
className="max-h-52 w-full rounded-md border border-border object-cover bg-muted" className="max-h-40 w-full rounded-md border border-border object-cover bg-muted"
loading="lazy" loading="lazy"
referrerPolicy="no-referrer" referrerPolicy="no-referrer"
/> />
@ -282,7 +296,7 @@ export default function UnknownNote({
{elevated.summary ? ( {elevated.summary ? (
<div> <div>
<p className="text-xs font-medium uppercase tracking-wide text-muted-foreground mb-1"> <p className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground mb-0.5">
{t('Summary')} {t('Summary')}
</p> </p>
<p className={cn(proseClass, 'text-muted-foreground')}>{truncatePreview(elevated.summary, CONTENT_PREVIEW_MAX)}</p> <p className={cn(proseClass, 'text-muted-foreground')}>{truncatePreview(elevated.summary, CONTENT_PREVIEW_MAX)}</p>
@ -291,7 +305,7 @@ export default function UnknownNote({
{elevated.description ? ( {elevated.description ? (
<div> <div>
<p className="text-xs font-medium uppercase tracking-wide text-muted-foreground mb-1"> <p className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground mb-0.5">
{t('Description')} {t('Description')}
</p> </p>
<p className={proseClass}>{truncatePreview(elevated.description, CONTENT_PREVIEW_MAX)}</p> <p className={proseClass}>{truncatePreview(elevated.description, CONTENT_PREVIEW_MAX)}</p>
@ -300,7 +314,7 @@ export default function UnknownNote({
{elevated.tagContent && normText(elevated.tagContent) !== contentNorm ? ( {elevated.tagContent && normText(elevated.tagContent) !== contentNorm ? (
<div> <div>
<p className="text-xs font-medium uppercase tracking-wide text-muted-foreground mb-1"> <p className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground mb-0.5">
{t('Unknown note tagged content')} {t('Unknown note tagged content')}
</p> </p>
<p className={proseClass}>{truncatePreview(elevated.tagContent, CONTENT_PREVIEW_MAX)}</p> <p className={proseClass}>{truncatePreview(elevated.tagContent, CONTENT_PREVIEW_MAX)}</p>
@ -322,17 +336,17 @@ export default function UnknownNote({
) : null} ) : null}
{showNoTextPlaceholder ? ( {showNoTextPlaceholder ? (
<p className="text-sm text-muted-foreground italic">{t('No text content in event')}</p> <p className="text-xs text-muted-foreground italic">{t('No text content in event')}</p>
) : null} ) : null}
{remainderTags.length > 0 ? ( {mainCardTags.length > 0 ? (
<div className="border-t border-border/80 pt-3"> <div className="border-t border-border/80 pt-2">
<p className="text-xs font-medium uppercase tracking-wide text-muted-foreground mb-2"> <p className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground mb-1">
{t('Tags')} {t('Tags')}
</p> </p>
<ul className="space-y-1.5 text-sm"> <ul className="space-y-0.5 text-xs">
{remainderTags.map((tag, i) => ( {mainCardTags.map((tag, i) => (
<li key={i} className="flex gap-2 rounded-md bg-muted/40 px-2 py-1.5"> <li key={i} className="flex gap-1.5 rounded bg-muted/40 px-1.5 py-0.5">
<span className="shrink-0 font-medium text-foreground/90">{tag[0]}</span> <span className="shrink-0 font-medium text-foreground/90">{tag[0]}</span>
<span className="min-w-0 break-all text-muted-foreground"> <span className="min-w-0 break-all text-muted-foreground">
{tag.length > 1 ? tag.slice(1).join(' · ') : '—'} {tag.length > 1 ? tag.slice(1).join(' · ') : '—'}
@ -362,7 +376,27 @@ export default function UnknownNote({
)} )}
</Button> </Button>
</CollapsibleTrigger> </CollapsibleTrigger>
<CollapsibleContent className="mt-2"> <CollapsibleContent className="mt-2 space-y-2">
{technicalReferenceTags.length > 0 ? (
<div className="rounded-md border border-border bg-muted/25 px-2 py-1.5">
<p className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground mb-1">
{t('Unknown note reference tags')}
</p>
<ul className="space-y-0.5 font-mono text-[11px] leading-snug text-muted-foreground">
{technicalReferenceTags.map((tag, i) => (
<li key={i} className="break-all">
<span className="text-foreground/80">{tag[0]}</span>
{tag.length > 1 ? (
<>
{' '}
{tag.slice(1).join(' · ')}
</>
) : null}
</li>
))}
</ul>
</div>
) : null}
<EventViewer event={displayEvent} /> <EventViewer event={displayEvent} />
</CollapsibleContent> </CollapsibleContent>
</Collapsible> </Collapsible>

89
src/components/NoteList/index.tsx

@ -61,6 +61,8 @@ import NoteCard, { NoteCardLoadingSkeleton } from '../NoteCard'
const LIMIT = 100 // Increased from 200 to load more events per request const LIMIT = 100 // Increased from 200 to load more events per request
const ALGO_LIMIT = 200 // Increased from 500 for algorithm feeds const ALGO_LIMIT = 200 // Increased from 500 for algorithm feeds
/** Single-relay explore: kindless REQ cap (relay returns whatever it has, up to this many). */
const RELAY_EXPLORE_LIMIT = 200
/** /**
* Vite HMR replaces this module and remounts NoteList; timeline refs reset while the subscription can briefly look * Vite HMR replaces this module and remounts NoteList; timeline refs reset while the subscription can briefly look
@ -97,11 +99,13 @@ function mergeEventBatchesById(prev: Event[], incoming: Event[], cap: number): E
/** When omitting `kinds` from a live REQ, require another scope so we never subscribe to a whole relay. */ /** When omitting `kinds` from a live REQ, require another scope so we never subscribe to a whole relay. */
function timelineFilterHasNonKindScope(f: Filter): boolean { function timelineFilterHasNonKindScope(f: Filter): boolean {
const search = f.search
return ( return (
(Array.isArray(f.authors) && f.authors.length > 0) || (Array.isArray(f.authors) && f.authors.length > 0) ||
(Array.isArray(f.ids) && f.ids.length > 0) || (Array.isArray(f.ids) && f.ids.length > 0) ||
(Array.isArray(f['#p']) && f['#p']!.length > 0) || (Array.isArray(f['#p']) && f['#p']!.length > 0) ||
(Array.isArray(f['#e']) && f['#e']!.length > 0) (Array.isArray(f['#e']) && f['#e']!.length > 0) ||
(typeof search === 'string' && search.trim().length > 0)
) )
} }
@ -114,6 +118,10 @@ const NoteList = forwardRef(
showKind1Replies = true, showKind1Replies = true,
showKind1111 = true, showKind1111 = true,
seeAllFeedEvents = false, seeAllFeedEvents = false,
/**
* Single-relay Explore / home chip: REQ omits `kinds`, limit 200, no feed kind filter (relay decides what to send).
*/
allowKindlessRelayExplore = false,
filterMutedNotes = true, filterMutedNotes = true,
hideReplies = false, hideReplies = false,
hideUntrustedNotes = false, hideUntrustedNotes = false,
@ -184,6 +192,7 @@ const NoteList = forwardRef(
showKind1111?: boolean showKind1111?: boolean
/** Omit REQ kinds and skip client-side kind filtering (main feed testing). Ignored when useFilterAsIs. */ /** Omit REQ kinds and skip client-side kind filtering (main feed testing). Ignored when useFilterAsIs. */
seeAllFeedEvents?: boolean seeAllFeedEvents?: boolean
allowKindlessRelayExplore?: boolean
filterMutedNotes?: boolean filterMutedNotes?: boolean
hideReplies?: boolean hideReplies?: boolean
hideUntrustedNotes?: boolean hideUntrustedNotes?: boolean
@ -376,11 +385,15 @@ const NoteList = forwardRef(
() => () =>
JSON.stringify({ JSON.stringify({
feed: timelineSubscriptionKey, feed: timelineSubscriptionKey,
...(allowKindlessRelayExplore
? { relayKindless: true }
: {
kinds: showKindsKey, kinds: showKindsKey,
op: showKind1OPs, op: showKind1OPs,
rep: showKind1Replies, rep: showKind1Replies,
c1111: showKind1111, c1111: showKind1111,
seeAll: seeAllFeedEvents seeAll: seeAllFeedEvents
})
}), }),
[ [
timelineSubscriptionKey, timelineSubscriptionKey,
@ -388,14 +401,22 @@ const NoteList = forwardRef(
showKind1OPs, showKind1OPs,
showKind1Replies, showKind1Replies,
showKind1111, showKind1111,
seeAllFeedEvents seeAllFeedEvents,
allowKindlessRelayExplore
] ]
) )
/** Kindless relay explore ignores the feed kind picker; avoid re-subscribing when it changes. */
const timelineResubscribeKindKey = allowKindlessRelayExplore
? 'kindless-relay-explore'
: `${showKindsKey}|${showKind1OPs}|${showKind1Replies}|${showKind1111}`
const showKindsRef = useRef(showKinds) const showKindsRef = useRef(showKinds)
showKindsRef.current = showKinds showKindsRef.current = showKinds
const seeAllFeedEventsRef = useRef(seeAllFeedEvents) const seeAllFeedEventsRef = useRef(seeAllFeedEvents)
seeAllFeedEventsRef.current = seeAllFeedEvents seeAllFeedEventsRef.current = seeAllFeedEvents
const allowKindlessRelayExploreRef = useRef(allowKindlessRelayExplore)
allowKindlessRelayExploreRef.current = allowKindlessRelayExplore
const useFilterAsIsRef = useRef(useFilterAsIs) const useFilterAsIsRef = useRef(useFilterAsIs)
useFilterAsIsRef.current = useFilterAsIs useFilterAsIsRef.current = useFilterAsIs
const clientSideKindFilterRef = useRef(clientSideKindFilter) const clientSideKindFilterRef = useRef(clientSideKindFilter)
@ -464,7 +485,7 @@ const NoteList = forwardRef(
const idSet = new Set<string>() const idSet = new Set<string>()
return events.slice(0, showCount).filter((evt) => { return events.slice(0, showCount).filter((evt) => {
if (!seeAllFeedEvents) { if (!seeAllFeedEvents && !allowKindlessRelayExplore) {
if (!showKinds.includes(evt.kind)) return false if (!showKinds.includes(evt.kind)) return false
// Kind 1: show only OPs if showKind1OPs, only replies if showKind1Replies // Kind 1: show only OPs if showKind1OPs, only replies if showKind1Replies
if (evt.kind === kinds.ShortTextNote) { if (evt.kind === kinds.ShortTextNote) {
@ -492,7 +513,8 @@ const NoteList = forwardRef(
showKind1OPs, showKind1OPs,
showKind1Replies, showKind1Replies,
showKind1111, showKind1111,
seeAllFeedEvents seeAllFeedEvents,
allowKindlessRelayExplore
]) ])
useLayoutEffect(() => { useLayoutEffect(() => {
@ -538,7 +560,7 @@ const NoteList = forwardRef(
const idSet = new Set<string>() const idSet = new Set<string>()
return newEvents.filter((event: Event) => { return newEvents.filter((event: Event) => {
if (!seeAllFeedEvents) { if (!seeAllFeedEvents && !allowKindlessRelayExplore) {
if (!showKinds.includes(event.kind)) return false if (!showKinds.includes(event.kind)) return false
if (event.kind === kinds.ShortTextNote) { if (event.kind === kinds.ShortTextNote) {
const isReply = isReplyNoteEvent(event) const isReply = isReplyNoteEvent(event)
@ -565,7 +587,8 @@ const NoteList = forwardRef(
showKind1OPs, showKind1OPs,
showKind1Replies, showKind1Replies,
showKind1111, showKind1111,
seeAllFeedEvents seeAllFeedEvents,
allowKindlessRelayExplore
]) ])
useLayoutEffect(() => { useLayoutEffect(() => {
@ -792,8 +815,16 @@ const NoteList = forwardRef(
const mappedSubRequests = subRequestsRef.current.map(({ urls, filter }) => { const mappedSubRequests = subRequestsRef.current.map(({ urls, filter }) => {
const baseLimit = filter.limit ?? (areAlgoRelays ? ALGO_LIMIT : LIMIT) const baseLimit = filter.limit ?? (areAlgoRelays ? ALGO_LIMIT : LIMIT)
if (useFilterAsIs) { if (useFilterAsIs) {
const finalFilter: Filter = { ...filter, limit: baseLimit }
const hasKindsInRequest = Array.isArray(filter.kinds) && filter.kinds.length > 0 const hasKindsInRequest = Array.isArray(filter.kinds) && filter.kinds.length > 0
if (allowKindlessRelayExplore && urls.length === 1 && !hasKindsInRequest) {
const finalFilter: Filter = {
...filter,
limit: filter.limit ?? RELAY_EXPLORE_LIMIT
}
delete finalFilter.kinds
return { urls, filter: finalFilter }
}
const finalFilter: Filter = { ...filter, limit: baseLimit }
if (clientSideKindFilter) { if (clientSideKindFilter) {
if (hasKindsInRequest) { if (hasKindsInRequest) {
finalFilter.kinds = filter.kinds finalFilter.kinds = filter.kinds
@ -828,10 +859,13 @@ const NoteList = forwardRef(
}) })
const filterMissingKinds = (f: Filter) => !f.kinds || f.kinds.length === 0 const filterMissingKinds = (f: Filter) => !f.kinds || f.kinds.length === 0
const invalidFilters = mappedSubRequests.filter(({ filter: f }) => { const invalidFilters = mappedSubRequests.filter(({ urls, filter: f }) => {
if (seeAllNoSpell) return false if (seeAllNoSpell) return false
if (!filterMissingKinds(f)) return false if (!filterMissingKinds(f)) return false
if (useFilterAsIs && clientSideKindFilter && timelineFilterHasNonKindScope(f)) return false if (useFilterAsIs && clientSideKindFilter && timelineFilterHasNonKindScope(f)) return false
if (useFilterAsIs && allowKindlessRelayExplore && urls.length === 1) {
return false
}
return true return true
}) })
if (invalidFilters.length > 0) { if (invalidFilters.length > 0) {
@ -849,7 +883,7 @@ const NoteList = forwardRef(
} }
const narrowLiveBatch = (evs: Event[]) => { const narrowLiveBatch = (evs: Event[]) => {
if (seeAllNoSpell) return evs if (seeAllFeedEventsRef.current || allowKindlessRelayExploreRef.current) return evs
if (!useFilterAsIs || !clientSideKindFilter) return evs if (!useFilterAsIs || !clientSideKindFilter) return evs
return evs.filter((e) => showKinds.includes(e.kind)) return evs.filter((e) => showKinds.includes(e.kind))
} }
@ -886,7 +920,12 @@ const NoteList = forwardRef(
let merged = [...byId.values()] let merged = [...byId.values()]
.sort((a, b) => b.created_at - a.created_at) .sort((a, b) => b.created_at - a.created_at)
.slice(0, cap) .slice(0, cap)
if (useFilterAsIs && clientSideKindFilter) { if (
useFilterAsIs &&
clientSideKindFilter &&
!seeAllFeedEventsRef.current &&
!allowKindlessRelayExploreRef.current
) {
merged = merged.filter((e) => showKinds.includes(e.kind)) merged = merged.filter((e) => showKinds.includes(e.kind))
} }
if (sessionSnap?.length && !userPulledRefresh) { if (sessionSnap?.length && !userPulledRefresh) {
@ -970,7 +1009,11 @@ const NoteList = forwardRef(
}, subscribeSetupRaceMs) }, subscribeSetupRaceMs)
}) })
const eventCap = areAlgoRelays ? ALGO_LIMIT : LIMIT const eventCap = allowKindlessRelayExplore
? RELAY_EXPLORE_LIMIT
: areAlgoRelays
? ALGO_LIMIT
: LIMIT
timelineSubscribePromise = client.subscribeTimeline( timelineSubscribePromise = client.subscribeTimeline(
mappedSubRequests as Array<{ urls: string[]; filter: TSubRequestFilter }>, mappedSubRequests as Array<{ urls: string[]; filter: TSubRequestFilter }>,
@ -1084,10 +1127,9 @@ const NoteList = forwardRef(
onNew: (event: Event) => { onNew: (event: Event) => {
if (!effectActive) return if (!effectActive) return
feedRelayReturnedAnyEventRef.current = true feedRelayReturnedAnyEventRef.current = true
const seeAll = seeAllFeedEventsRef.current && !useFilterAsIs if (!seeAllFeedEventsRef.current && !allowKindlessRelayExploreRef.current) {
if (!seeAll && !useFilterAsIs && !showKinds.includes(event.kind)) return if (!useFilterAsIs && !showKinds.includes(event.kind)) return
if (clientSideKindFilter && useFilterAsIs && !showKinds.includes(event.kind)) return if (clientSideKindFilter && useFilterAsIs && !showKinds.includes(event.kind)) return
if (!seeAll) {
if (event.kind === kinds.ShortTextNote) { if (event.kind === kinds.ShortTextNote) {
const isReply = isReplyNoteEvent(event) const isReply = isReplyNoteEvent(event)
if (isReply && !showKind1Replies) return if (isReply && !showKind1Replies) return
@ -1179,10 +1221,7 @@ const NoteList = forwardRef(
mergeTimelineWhenSubRequestFiltersMatch, mergeTimelineWhenSubRequestFiltersMatch,
feedTimelineScopeKey, feedTimelineScopeKey,
refreshCount, refreshCount,
showKindsKey, timelineResubscribeKindKey,
showKind1OPs,
showKind1Replies,
showKind1111,
seeAllFeedEvents, seeAllFeedEvents,
useFilterAsIs, useFilterAsIs,
areAlgoRelays, areAlgoRelays,
@ -1194,7 +1233,8 @@ const NoteList = forwardRef(
oneShotGlobalTimeoutMs, oneShotGlobalTimeoutMs,
oneShotEoseTimeoutMs, oneShotEoseTimeoutMs,
oneShotFirstRelayGraceMs, oneShotFirstRelayGraceMs,
clientSideKindFilter clientSideKindFilter,
allowKindlessRelayExplore
]) ])
const oneShotDebugPrevLoadingRef = useRef(false) const oneShotDebugPrevLoadingRef = useRef(false)
@ -1458,14 +1498,17 @@ const NoteList = forwardRef(
} }
let fetchBatch = newEvents let fetchBatch = newEvents
let toAppend = const narrowLoadMore =
useFilterAsIsRef.current && clientSideKindFilterRef.current useFilterAsIsRef.current &&
clientSideKindFilterRef.current &&
!seeAllFeedEventsRef.current &&
!allowKindlessRelayExploreRef.current
let toAppend = narrowLoadMore
? fetchBatch.filter((e) => showKindsRef.current.includes(e.kind)) ? fetchBatch.filter((e) => showKindsRef.current.includes(e.kind))
: fetchBatch : fetchBatch
if ( if (
useFilterAsIsRef.current && narrowLoadMore &&
clientSideKindFilterRef.current &&
toAppend.length === 0 && toAppend.length === 0 &&
fetchBatch.length > 0 fetchBatch.length > 0
) { ) {

2
src/components/Relay/index.tsx

@ -82,6 +82,8 @@ const Relay = forwardRef<TNoteListRef, { url?: string; className?: string }>(fun
subRequests={[ subRequests={[
{ urls: [normalizedUrl], filter: debouncedInput ? { search: debouncedInput } : {} } { urls: [normalizedUrl], filter: debouncedInput ? { search: debouncedInput } : {} }
]} ]}
useFilterAsIs
allowKindlessRelayExplore
/> />
</div> </div>
) )

1
src/i18n/locales/de.ts

@ -1437,6 +1437,7 @@ export default {
'Subscribed to topic (local)': 'Subscribed to topic (local)', 'Subscribed to topic (local)': 'Subscribed to topic (local)',
'Subscribing...': 'Subscribing...', 'Subscribing...': 'Subscribing...',
Summary: 'Summary', Summary: 'Summary',
'Unknown note reference tags': 'Referenz-Tags (e, p, q, a)',
'Supported Event Types': 'Supported Event Types', 'Supported Event Types': 'Supported Event Types',
'Take a note': 'Take a note', 'Take a note': 'Take a note',
'The full prompt conversation (optional)': 'The full prompt conversation (optional)', 'The full prompt conversation (optional)': 'The full prompt conversation (optional)',

1
src/i18n/locales/en.ts

@ -412,6 +412,7 @@ export default {
'Unknown note declared kind tag': 'Tagged kind: {{value}}', 'Unknown note declared kind tag': 'Tagged kind: {{value}}',
'Unknown note tagged pubkey': 'Tagged pubkey', 'Unknown note tagged pubkey': 'Tagged pubkey',
'Unknown note tagged content': 'Content', 'Unknown note tagged content': 'Content',
'Unknown note reference tags': 'Reference tags (e, p, q, a)',
'Copy JSON': 'Copy JSON', 'Copy JSON': 'Copy JSON',
Verse: 'Verse', Verse: 'Verse',
'Notification reaction summary': 'reacted to this note.', 'Notification reaction summary': 'reacted to this note.',

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

@ -75,6 +75,10 @@ const RelaysFeed = forwardRef<
? showKinds ? showKinds
: [kinds.ShortTextNote] : [kinds.ShortTextNote]
/** One relay + user kind filter: avoid huge `kinds` REQ (many relays error with "too many kinds"). */
const singleRelayKindlessExplore =
feedInfo.feedType === 'relay' && relayUrls.length === 1 && !kindsOverride?.length
const canRenderFeed = const canRenderFeed =
(feedInfo.feedType === 'relay' || (feedInfo.feedType === 'relay' ||
feedInfo.feedType === 'relays' || feedInfo.feedType === 'relays' ||
@ -95,6 +99,9 @@ const RelaysFeed = forwardRef<
// Hooks must run every render — never place useMemo after conditional returns. // Hooks must run every render — never place useMemo after conditional returns.
const subRequests = useMemo(() => { const subRequests = useMemo(() => {
if (!canRenderFeed) return [] if (!canRenderFeed) return []
if (singleRelayKindlessExplore) {
return [{ urls: relayUrls, filter: {} }]
}
return [ return [
{ {
urls: relayUrls, urls: relayUrls,
@ -103,7 +110,7 @@ const RelaysFeed = forwardRef<
} }
} }
] ]
}, [canRenderFeed, relayUrls, defaultKinds, kindsOverride]) }, [canRenderFeed, relayUrls, defaultKinds, kindsOverride, singleRelayKindlessExplore])
if (!canRenderFeed) { if (!canRenderFeed) {
return null return null
@ -123,6 +130,8 @@ const RelaysFeed = forwardRef<
onSubHeaderRefresh={onSubHeaderRefresh} onSubHeaderRefresh={onSubHeaderRefresh}
preserveTimelineOnSubRequestsChange preserveTimelineOnSubRequestsChange
feedTimelineScopeKey={feedTimelineScopeKey} feedTimelineScopeKey={feedTimelineScopeKey}
useFilterAsIs={singleRelayKindlessExplore}
allowKindlessRelayExplore={singleRelayKindlessExplore}
/> />
) )
}) })

Loading…
Cancel
Save