diff --git a/src/components/ReqViewer.tsx b/src/components/ReqViewer.tsx index d683d72..7c3a827 100644 --- a/src/components/ReqViewer.tsx +++ b/src/components/ReqViewer.tsx @@ -88,7 +88,7 @@ import { } from "@/lib/req-state-machine"; import { resolveFilterAliases, getTagValues } from "@/lib/nostr-utils"; import { chunkFiltersByRelay } from "@/lib/relay-filter-chunking"; -import { useStableValue } from "@/hooks/useStable"; +import { useStableRelayFilterMap } from "@/hooks/useStable"; import { useNostrEvent } from "@/hooks/useNostrEvent"; import { MemoizedCompactEventRow } from "./nostr/CompactEventRow"; @@ -694,17 +694,28 @@ function QueryDropdown({
{Object.entries(relayFilterMap).map( ([relayUrl, relayFilters]) => { - const authorCount = relayFilters.reduce( + const reasoning = relayReasoning?.find( + (r) => r.relay === relayUrl, + ); + const isFallback = !!reasoning?.isFallback; + + // Use reasoning counts (assigned users) not raw filter counts + // (which include unassigned users piggybacking on every relay) + const assignedWriters = reasoning?.writers?.length || 0; + const assignedReaders = reasoning?.readers?.length || 0; + + // Total in chunked filter for unassigned calculation + const totalAuthors = relayFilters.reduce( (sum, f) => sum + (f.authors?.length || 0), 0, ); - const pTagCount = relayFilters.reduce( + const totalPTags = relayFilters.reduce( (sum, f) => sum + (f["#p"]?.length || 0), 0, ); - const isFallback = !!relayReasoning?.find( - (r) => r.relay === relayUrl, - )?.isFallback; + const unassignedAuthors = totalAuthors - assignedWriters; + const unassignedPTags = totalPTags - assignedReaders; + const relayJson = JSON.stringify( relayFilters.length === 1 ? relayFilters[0] : relayFilters, null, @@ -718,22 +729,42 @@ function QueryDropdown({
- {authorCount > 0 && ( + {(assignedWriters > 0 || + (isFallback && totalAuthors > 0)) && ( 0 ? ` + ${unassignedAuthors} unassigned` : ""} (${totalAuthors} total in REQ)` + } > - {authorCount} + {isFallback ? totalAuthors : assignedWriters} + {!isFallback && unassignedAuthors > 0 && ( + + +{unassignedAuthors} + + )} )} - {pTagCount > 0 && ( + {(assignedReaders > 0 || + (isFallback && totalPTags > 0)) && ( 0 ? ` + ${unassignedPTags} unassigned` : ""} (${totalPTags} total in REQ)` + } > - {pTagCount} + {isFallback ? totalPTags : assignedReaders} + {!isFallback && unassignedPTags > 0 && ( + + +{unassignedPTags} + + )} )} {isFallback && ( @@ -850,22 +881,21 @@ export default function ReqViewer({ // We just display the NIP-05 identifiers for user reference // NIP-65 outbox relay selection - // Memoize fallbackRelays to prevent re-creation on every render - const fallbackRelays = useMemo( - () => - state.activeAccount?.relays?.filter((r) => r.read).map((r) => r.url) || - AGGREGATOR_RELAYS, - [state.activeAccount?.relays], - ); + // Fallback relays for follows without kind:10002 relay lists. + // Use AGGREGATOR_RELAYS (popular general relays), NOT the user's personal relays. + // The user's relays (both read and write) are specific to their network — + // assigning them as outbox for hundreds of unknown follows inflates counts + // and sends unnecessary queries to small/niche relays. + const fallbackRelays = AGGREGATOR_RELAYS; - // Memoize outbox options to prevent object re-creation + // Stable outbox options (fallbackRelays is a module constant) const outboxOptions = useMemo( () => ({ fallbackRelays, timeout: 1000, maxRelays: 42, }), - [fallbackRelays], + [], ); // Select optimal relays based on authors (write relays) and #p tags (read relays) @@ -916,7 +946,7 @@ export default function ReqViewer({ return chunkFiltersByRelay(resolvedFilter, reasoning); }, [relays, reasoning, resolvedFilter]); - const stableRelayFilterMap = useStableValue(relayFilterMap); + const stableRelayFilterMap = useStableRelayFilterMap(relayFilterMap); const { events,