From 5c2adbee4c0eb63d01b8b96237f45cd63d81b93f Mon Sep 17 00:00:00 2001 From: Silberengel Date: Sun, 29 Mar 2026 14:37:48 +0200 Subject: [PATCH] fix publications --- src/hooks/usePublicationSectionLoader.ts | 82 +++++++++++++++---- src/lib/publication-section-fetch.ts | 39 ++++++++- src/pages/primary/NoteListPage/RelaysFeed.tsx | 4 +- src/providers/KindFilterProvider.tsx | 29 ++++++- 4 files changed, 134 insertions(+), 20 deletions(-) diff --git a/src/hooks/usePublicationSectionLoader.ts b/src/hooks/usePublicationSectionLoader.ts index 68fa7a16..5e88e923 100644 --- a/src/hooks/usePublicationSectionLoader.ts +++ b/src/hooks/usePublicationSectionLoader.ts @@ -9,10 +9,18 @@ import { generateBech32IdFromATag } from '@/lib/tag' import { isReplaceableEvent } from '@/lib/event' import client from '@/services/client.service' import { eventService } from '@/services/client.service' +import logger from '@/lib/logger' import indexedDb from '@/services/indexed-db.service' import type { Event } from 'nostr-tools' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +const PUB_SEC_LOG = '[PublicationSection]' +function pubLog(message: string, data?: Record) { + if (!import.meta.env.DEV) return + if (data) logger.info(`${PUB_SEC_LOG} ${message}`, data) + else logger.info(`${PUB_SEC_LOG} ${message}`) +} + export type SectionLoadStatus = 'idle' | 'loading' | 'loaded' | 'error' export type PublicationSectionRow = { @@ -134,6 +142,11 @@ export function usePublicationSectionLoader(indexEvent: Event, referencesData: P if (refsToLoad.length === 0) return + pubLog('flush_start', { + keys: refsToLoad.map((r) => refKey(r)), + relayCount: relayUrlsRef.current.length + }) + setRows((prev) => { const next = new Map(prev) for (const ref of refsToLoad) { @@ -147,25 +160,53 @@ export function usePublicationSectionLoader(indexEvent: Event, referencesData: P const urls = relayUrlsRef.current const resolved = new Map() - if (urls.length > 0) { - const fromDb = await hydrateRefsFromIndexedDb(refsToLoad) - for (const [k, ev] of fromDb) { - resolved.set(k, ev) - client.addEventToCache(ev) - } + // Always hydrate from IDB — do not gate on relay URLs (they resolve async after first IO batch). + const fromDb = await hydrateRefsFromIndexedDb(refsToLoad) + for (const [k, ev] of fromDb) { + resolved.set(k, ev) + client.addEventToCache(ev) + } - const stillNeed = refsToLoad.filter((r) => !resolved.has(refKey(r))) - if (stillNeed.length > 0) { - const fromNet = await batchFetchPublicationSectionEvents(stillNeed, urls) - for (const [k, ev] of fromNet) { - resolved.set(k, ev) - client.addEventToCache(ev) - if (isReplaceableEvent(ev.kind)) void indexedDb.putReplaceableEvent(ev) + let stillNeed = refsToLoad.filter((r) => !resolved.has(refKey(r))) + pubLog('after_idb', { + fromDb: fromDb.size, + stillNeed: stillNeed.map((r) => ({ key: refKey(r), type: r.type })) + }) + + // No relay list yet: apply DB hits only, re-queue the rest (do not mark error). + if (urls.length === 0 && stillNeed.length > 0) { + for (const r of stillNeed) pendingRef.current.add(refKey(r)) + pubLog('defer_net_until_relays', { reQueued: stillNeed.length }) + setRows((prev) => { + const next = new Map(prev) + for (const ref of refsToLoad) { + const k = refKey(ref) + const row = next.get(k) + if (!row) continue + const ev = resolved.get(k) + if (ev) next.set(k, { ...row, event: ev, status: 'loaded' }) + else next.set(k, { ...row, status: 'idle', event: undefined }) } + return next + }) + return + } + + if (urls.length > 0 && stillNeed.length > 0) { + const fromNet = await batchFetchPublicationSectionEvents(stillNeed, urls) + pubLog('after_batch_fetch', { fromNet: fromNet.size }) + for (const [k, ev] of fromNet) { + resolved.set(k, ev) + client.addEventToCache(ev) + if (isReplaceableEvent(ev.kind)) void indexedDb.putReplaceableEvent(ev) } } const missing = refsToLoad.filter((r) => !resolved.has(refKey(r))) + pubLog('before_fallback', { + missing: missing.map((r) => refKey(r)), + relayUrlsEmpty: urls.length === 0 + }) await Promise.all( missing.map(async (ref) => { const k = refKey(ref) @@ -178,6 +219,17 @@ export function usePublicationSectionLoader(indexEvent: Event, referencesData: P }) ) + const failed = refsToLoad.filter((r) => !resolved.has(refKey(r))) + pubLog('flush_done', { + loaded: refsToLoad.length - failed.length, + failed: failed.map((r) => ({ + key: refKey(r), + type: r.type, + coordinate: r.coordinate, + eventId: r.eventId + })) + }) + setRows((prev) => { const next = new Map(prev) for (const ref of refsToLoad) { @@ -223,8 +275,8 @@ export function usePublicationSectionLoader(indexEvent: Event, referencesData: P useEffect(() => { if (!relayReady || orderedKeys.length === 0) return - const n = Math.min(3, orderedKeys.length) - requestKeys(orderedKeys.slice(0, n)) + // Full list: scroll-IO may have fired before relays were ready; those keys were re-queued idle. + requestKeys(orderedKeys) }, [relayReady, orderedKeys, requestKeys]) const failedKeys = useMemo( diff --git a/src/lib/publication-section-fetch.ts b/src/lib/publication-section-fetch.ts index dad66662..e8739492 100644 --- a/src/lib/publication-section-fetch.ts +++ b/src/lib/publication-section-fetch.ts @@ -1,3 +1,4 @@ +import logger from '@/lib/logger' import { publicationCoordinateLookupKeys } from '@/lib/publication-coordinate' import { buildComprehensiveRelayList } from '@/lib/relay-list-builder' import { normalizeUrl } from '@/lib/url' @@ -165,7 +166,15 @@ export async function batchFetchPublicationSectionEvents( } } - if (filters.length === 0) return out + if (filters.length === 0) { + if (import.meta.env.DEV) { + logger.info('[PublicationSection] batch_fetch_skip — no filters', { + aRefCount: aRefs.length, + idRefCount: idRefs.length + }) + } + return out + } let events: Event[] = [] try { @@ -175,7 +184,14 @@ export async function batchFetchPublicationSectionEvents( /** Do not early-resolve after the first event; this query must wait for the full batch. */ firstRelayResultGraceMs: false }) - } catch { + } catch (err) { + if (import.meta.env.DEV) { + logger.warn('[PublicationSection] batch_fetch_error', { + message: err instanceof Error ? err.message : String(err), + filterCount: filters.length, + relayCount: relayUrls.length + }) + } return out } @@ -211,5 +227,24 @@ export async function batchFetchPublicationSectionEvents( if (ev) out.set(key, ev) } + if (import.meta.env.DEV) { + const unmatchedA = aRefs.filter((r) => !out.has(publicationRefKey(r))) + const unmatchedE = idRefs.filter((r) => !out.has(publicationRefKey(r))) + logger.info('[PublicationSection] batch_fetch_result', { + relayCount: relayUrls.length, + filterCount: filters.length, + eventsReturned: events.length, + byCoordSize: byCoord.size, + resolved: out.size, + unmatchedACount: unmatchedA.length, + unmatchedECount: unmatchedE.length, + unmatchedAKeys: unmatchedA.map((r) => publicationRefKey(r)).slice(0, 12), + sampleEventCoords: events.slice(0, 3).map((ev) => { + const d = ev.tags.find((t) => t[0] === 'd')?.[1] + return d !== undefined && d !== '' ? coordinateFromEvent(ev) : `${ev.kind}:${ev.pubkey.slice(0, 8)}…` + }) + }) + } + return out } diff --git a/src/pages/primary/NoteListPage/RelaysFeed.tsx b/src/pages/primary/NoteListPage/RelaysFeed.tsx index abb757ee..d3b45855 100644 --- a/src/pages/primary/NoteListPage/RelaysFeed.tsx +++ b/src/pages/primary/NoteListPage/RelaysFeed.tsx @@ -3,7 +3,7 @@ import type { TNoteListRef } from '@/components/NoteList' import { checkAlgoRelay } from '@/lib/relay' import { normalizeUrl } from '@/lib/url' import { useFeed } from '@/providers/FeedProvider' -import { useKindFilter } from '@/providers/KindFilterProvider' +import { useKindFilterOrDefaults } from '@/providers/KindFilterProvider' import relayInfoService from '@/services/relay-info.service' import { kinds } from 'nostr-tools' import React, { forwardRef, useEffect, useMemo, useState } from 'react' @@ -18,7 +18,7 @@ const RelaysFeed = forwardRef< } >(function RelaysFeed({ setSubHeader, onSubHeaderRefresh, kindsOverride }, ref) { const { feedInfo, relayUrls } = useFeed() - const { showKinds } = useKindFilter() + const { showKinds } = useKindFilterOrDefaults() const [areAlgoRelays, setAreAlgoRelays] = useState(false) const [relayAlgoReady, setRelayAlgoReady] = useState(false) diff --git a/src/providers/KindFilterProvider.tsx b/src/providers/KindFilterProvider.tsx index 4ed12870..eb8bde50 100644 --- a/src/providers/KindFilterProvider.tsx +++ b/src/providers/KindFilterProvider.tsx @@ -1,4 +1,5 @@ import { createContext, useContext, useState, useCallback, useMemo } from 'react' +import type { ReactNode } from 'react' import storage from '@/services/local-storage.service' import { DEFAULT_FEED_SHOW_KINDS, ExtendedKind } from '@/constants' import { kinds } from 'nostr-tools' @@ -53,7 +54,33 @@ export const useKindFilter = () => { return context } -export function KindFilterProvider({ children }: { children: React.ReactNode }) { +/** When context is missing (e.g. Vite HMR / duplicate module instances), use storage-backed defaults. */ +function createKindFilterFallback(): TKindFilterContext { + const defaultShowKinds = DEFAULT_FEED_SHOW_KINDS + const storedShowKinds = storage.getShowKinds() + const showKinds = storedShowKinds.length > 0 ? storedShowKinds : defaultShowKinds + const noop = () => {} + return { + showKinds, + showKind1OPs: storage.getShowKind1OPs(), + showKind1Replies: storage.getShowKind1Replies(), + showKind1111: storage.getShowKind1111(), + feedKindFilterBypass: storage.getFeedKindFilterBypass(), + updateShowKinds: noop, + updateShowKind1OPs: noop, + updateShowKind1Replies: noop, + updateShowKind1111: noop, + updateFeedKindFilterBypass: noop + } +} + +export function useKindFilterOrDefaults(): TKindFilterContext { + const context = useContext(KindFilterContext) + const fallback = useMemo(() => createKindFilterFallback(), []) + return context ?? fallback +} + +export function KindFilterProvider({ children }: { children: ReactNode }) { const defaultShowKinds = DEFAULT_FEED_SHOW_KINDS const storedShowKinds = storage.getShowKinds() const storedShowKind1OPs = storage.getShowKind1OPs()