mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-06-04 17:51:12 +02:00
fix: use aggregator relays as NIP-65 fallback and show accurate per-relay counts
- Use AGGREGATOR_RELAYS as fallback for follows without kind:10002, not the user's personal relays. Personal inbox/write relays were being assigned as outbox for hundreds of unknown follows, inflating counts and sending unnecessary queries to niche relays. - Per-relay REQ badges now show assigned count (from reasoning) as the primary number, with unassigned users shown dimmed as +N. Tooltips show the full breakdown. - Switch to useStableRelayFilterMap for structural comparison. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -88,7 +88,7 @@ import {
|
|||||||
} from "@/lib/req-state-machine";
|
} from "@/lib/req-state-machine";
|
||||||
import { resolveFilterAliases, getTagValues } from "@/lib/nostr-utils";
|
import { resolveFilterAliases, getTagValues } from "@/lib/nostr-utils";
|
||||||
import { chunkFiltersByRelay } from "@/lib/relay-filter-chunking";
|
import { chunkFiltersByRelay } from "@/lib/relay-filter-chunking";
|
||||||
import { useStableValue } from "@/hooks/useStable";
|
import { useStableRelayFilterMap } from "@/hooks/useStable";
|
||||||
|
|
||||||
import { useNostrEvent } from "@/hooks/useNostrEvent";
|
import { useNostrEvent } from "@/hooks/useNostrEvent";
|
||||||
import { MemoizedCompactEventRow } from "./nostr/CompactEventRow";
|
import { MemoizedCompactEventRow } from "./nostr/CompactEventRow";
|
||||||
@@ -694,17 +694,28 @@ function QueryDropdown({
|
|||||||
<div className="mt-1 pl-1">
|
<div className="mt-1 pl-1">
|
||||||
{Object.entries(relayFilterMap).map(
|
{Object.entries(relayFilterMap).map(
|
||||||
([relayUrl, relayFilters]) => {
|
([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),
|
(sum, f) => sum + (f.authors?.length || 0),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
const pTagCount = relayFilters.reduce(
|
const totalPTags = relayFilters.reduce(
|
||||||
(sum, f) => sum + (f["#p"]?.length || 0),
|
(sum, f) => sum + (f["#p"]?.length || 0),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
const isFallback = !!relayReasoning?.find(
|
const unassignedAuthors = totalAuthors - assignedWriters;
|
||||||
(r) => r.relay === relayUrl,
|
const unassignedPTags = totalPTags - assignedReaders;
|
||||||
)?.isFallback;
|
|
||||||
const relayJson = JSON.stringify(
|
const relayJson = JSON.stringify(
|
||||||
relayFilters.length === 1 ? relayFilters[0] : relayFilters,
|
relayFilters.length === 1 ? relayFilters[0] : relayFilters,
|
||||||
null,
|
null,
|
||||||
@@ -718,22 +729,42 @@ function QueryDropdown({
|
|||||||
<RelayLink url={relayUrl} showInboxOutbox={false} />
|
<RelayLink url={relayUrl} showInboxOutbox={false} />
|
||||||
</div>
|
</div>
|
||||||
<div className="shrink-0 text-muted-foreground flex items-center gap-1.5 text-[10px]">
|
<div className="shrink-0 text-muted-foreground flex items-center gap-1.5 text-[10px]">
|
||||||
{authorCount > 0 && (
|
{(assignedWriters > 0 ||
|
||||||
|
(isFallback && totalAuthors > 0)) && (
|
||||||
<span
|
<span
|
||||||
className="flex items-center gap-0.5"
|
className="flex items-center gap-0.5"
|
||||||
title="outbox / write"
|
title={
|
||||||
|
isFallback
|
||||||
|
? `${totalAuthors} authors (fallback relay)`
|
||||||
|
: `${assignedWriters} assigned outbox writers${unassignedAuthors > 0 ? ` + ${unassignedAuthors} unassigned` : ""} (${totalAuthors} total in REQ)`
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Send className="size-2.5" />
|
<Send className="size-2.5" />
|
||||||
{authorCount}
|
{isFallback ? totalAuthors : assignedWriters}
|
||||||
|
{!isFallback && unassignedAuthors > 0 && (
|
||||||
|
<span className="text-muted-foreground/50">
|
||||||
|
+{unassignedAuthors}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{pTagCount > 0 && (
|
{(assignedReaders > 0 ||
|
||||||
|
(isFallback && totalPTags > 0)) && (
|
||||||
<span
|
<span
|
||||||
className="flex items-center gap-0.5"
|
className="flex items-center gap-0.5"
|
||||||
title="inbox / read"
|
title={
|
||||||
|
isFallback
|
||||||
|
? `${totalPTags} mentions (fallback relay)`
|
||||||
|
: `${assignedReaders} assigned inbox readers${unassignedPTags > 0 ? ` + ${unassignedPTags} unassigned` : ""} (${totalPTags} total in REQ)`
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Inbox className="size-2.5" />
|
<Inbox className="size-2.5" />
|
||||||
{pTagCount}
|
{isFallback ? totalPTags : assignedReaders}
|
||||||
|
{!isFallback && unassignedPTags > 0 && (
|
||||||
|
<span className="text-muted-foreground/50">
|
||||||
|
+{unassignedPTags}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{isFallback && (
|
{isFallback && (
|
||||||
@@ -850,22 +881,21 @@ export default function ReqViewer({
|
|||||||
// We just display the NIP-05 identifiers for user reference
|
// We just display the NIP-05 identifiers for user reference
|
||||||
|
|
||||||
// NIP-65 outbox relay selection
|
// NIP-65 outbox relay selection
|
||||||
// Memoize fallbackRelays to prevent re-creation on every render
|
// Fallback relays for follows without kind:10002 relay lists.
|
||||||
const fallbackRelays = useMemo(
|
// 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 —
|
||||||
state.activeAccount?.relays?.filter((r) => r.read).map((r) => r.url) ||
|
// assigning them as outbox for hundreds of unknown follows inflates counts
|
||||||
AGGREGATOR_RELAYS,
|
// and sends unnecessary queries to small/niche relays.
|
||||||
[state.activeAccount?.relays],
|
const fallbackRelays = AGGREGATOR_RELAYS;
|
||||||
);
|
|
||||||
|
|
||||||
// Memoize outbox options to prevent object re-creation
|
// Stable outbox options (fallbackRelays is a module constant)
|
||||||
const outboxOptions = useMemo(
|
const outboxOptions = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
fallbackRelays,
|
fallbackRelays,
|
||||||
timeout: 1000,
|
timeout: 1000,
|
||||||
maxRelays: 42,
|
maxRelays: 42,
|
||||||
}),
|
}),
|
||||||
[fallbackRelays],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Select optimal relays based on authors (write relays) and #p tags (read relays)
|
// 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);
|
return chunkFiltersByRelay(resolvedFilter, reasoning);
|
||||||
}, [relays, reasoning, resolvedFilter]);
|
}, [relays, reasoning, resolvedFilter]);
|
||||||
|
|
||||||
const stableRelayFilterMap = useStableValue(relayFilterMap);
|
const stableRelayFilterMap = useStableRelayFilterMap(relayFilterMap);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
events,
|
events,
|
||||||
|
|||||||
Reference in New Issue
Block a user