diff --git a/src/PageManager.tsx b/src/PageManager.tsx index 36e48067..86aedf3b 100644 --- a/src/PageManager.tsx +++ b/src/PageManager.tsx @@ -1563,16 +1563,16 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) { window.history.go(-stackLength) } - if (isSmallScreen) { - return ( - + const primaryPageContextValue: TPrimaryPageContext = { + navigate: navigatePrimaryPage, + current: currentPrimaryPage, + currentPageProps, + display: isSmallScreen ? secondaryStack.length === 0 : true + } + + return ( + + {isSmallScreen ? ( - - ) - } - - return ( - + ) : ( + )} ) } diff --git a/src/components/NoteList/index.tsx b/src/components/NoteList/index.tsx index 62da40ae..13092cdd 100644 --- a/src/components/NoteList/index.tsx +++ b/src/components/NoteList/index.tsx @@ -531,9 +531,10 @@ const NoteList = forwardRef( const batches = await Promise.all( mappedSubRequests.map(({ urls, filter }) => client.fetchEvents(urls, filter, { - firstRelayResultGraceMs: false, + // Was `false`, which disabled feed grace and forced a wait for every relay EOSE (very slow). + firstRelayResultGraceMs: FIRST_RELAY_RESULT_GRACE_MS, globalTimeout: 14_000, - eoseTimeout: 800, + eoseTimeout: 2_000, cache: true }) ) @@ -1140,6 +1141,10 @@ const NoteList = forwardRef( ) : events.length > 0 ? (
{t('no more notes')}
+ ) : (spellFetchTimeoutMs != null && spellFetchTimeoutMs > 0) || oneShotFetch ? ( +
+ {t('No posts loaded for this feed. Try refreshing.')} +
) : (
)} diff --git a/src/i18n/locales/ar.ts b/src/i18n/locales/ar.ts index 1dd51c7e..6d16b9af 100644 --- a/src/i18n/locales/ar.ts +++ b/src/i18n/locales/ar.ts @@ -579,6 +579,8 @@ export default { 'No bookmarked notes with id tags yet. Only classic (e-tag) bookmarks load in this feed.', 'No follows or relays to load yet.': 'No follows or relays to load yet.', 'Nothing to load for this feed.': 'Nothing to load for this feed.', + 'No posts loaded for this feed. Try refreshing.': + 'No posts loaded for this feed. Try refreshing.', 'Republish to ...': 'إعادة النشر إلى ...', 'All available relays': 'All available relays', 'All active relays (monitoring list)': 'All active relays (monitoring list)', diff --git a/src/i18n/locales/de.ts b/src/i18n/locales/de.ts index 8bd1dafb..5cda8bd1 100644 --- a/src/i18n/locales/de.ts +++ b/src/i18n/locales/de.ts @@ -602,6 +602,8 @@ export default { 'Noch keine Lesezeichen mit Ereignis-IDs. Nur klassische (e-Tag-) Lesezeichen erscheinen in diesem Feed.', 'No follows or relays to load yet.': 'Noch keine Follows oder Relays zum Laden.', 'Nothing to load for this feed.': 'Für diesen Feed gibt es nichts zu laden.', + 'No posts loaded for this feed. Try refreshing.': + 'Keine Beiträge für diesen Feed geladen. Bitte aktualisieren.', 'Republish to ...': 'Erneut veröffentlichen zu ...', 'All available relays': 'All available relays', 'All active relays (monitoring list)': 'All active relays (monitoring list)', diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 5fbdda80..0622bcf6 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -591,6 +591,8 @@ export default { 'No bookmarked notes with id tags yet. Only classic (e-tag) bookmarks load in this feed.', 'No follows or relays to load yet.': 'No follows or relays to load yet.', 'Nothing to load for this feed.': 'Nothing to load for this feed.', + 'No posts loaded for this feed. Try refreshing.': + 'No posts loaded for this feed. Try refreshing.', 'Republish to ...': 'Republish to ...', 'All available relays': 'All available relays', 'All active relays (monitoring list)': 'All active relays (monitoring list)', diff --git a/src/i18n/locales/es.ts b/src/i18n/locales/es.ts index efa4b5a6..e831efad 100644 --- a/src/i18n/locales/es.ts +++ b/src/i18n/locales/es.ts @@ -584,6 +584,8 @@ export default { 'No bookmarked notes with id tags yet. Only classic (e-tag) bookmarks load in this feed.', 'No follows or relays to load yet.': 'No follows or relays to load yet.', 'Nothing to load for this feed.': 'Nothing to load for this feed.', + 'No posts loaded for this feed. Try refreshing.': + 'No posts loaded for this feed. Try refreshing.', 'Republish to ...': 'Republicar a ...', 'All available relays': 'All available relays', 'All active relays (monitoring list)': 'All active relays (monitoring list)', diff --git a/src/i18n/locales/fa.ts b/src/i18n/locales/fa.ts index b3fe52cd..b1f73833 100644 --- a/src/i18n/locales/fa.ts +++ b/src/i18n/locales/fa.ts @@ -582,6 +582,8 @@ export default { 'No bookmarked notes with id tags yet. Only classic (e-tag) bookmarks load in this feed.', 'No follows or relays to load yet.': 'No follows or relays to load yet.', 'Nothing to load for this feed.': 'Nothing to load for this feed.', + 'No posts loaded for this feed. Try refreshing.': + 'No posts loaded for this feed. Try refreshing.', 'Republish to ...': 'بازنشر به ...', 'All available relays': 'All available relays', 'All active relays (monitoring list)': 'All active relays (monitoring list)', diff --git a/src/i18n/locales/fr.ts b/src/i18n/locales/fr.ts index 13ed5aaf..a5117b59 100644 --- a/src/i18n/locales/fr.ts +++ b/src/i18n/locales/fr.ts @@ -585,6 +585,8 @@ export default { 'No bookmarked notes with id tags yet. Only classic (e-tag) bookmarks load in this feed.', 'No follows or relays to load yet.': 'No follows or relays to load yet.', 'Nothing to load for this feed.': 'Nothing to load for this feed.', + 'No posts loaded for this feed. Try refreshing.': + 'No posts loaded for this feed. Try refreshing.', 'Republish to ...': 'Reposter vers ...', 'All available relays': 'All available relays', 'All active relays (monitoring list)': 'All active relays (monitoring list)', diff --git a/src/i18n/locales/hi.ts b/src/i18n/locales/hi.ts index 72e963f2..12fb8d9d 100644 --- a/src/i18n/locales/hi.ts +++ b/src/i18n/locales/hi.ts @@ -583,6 +583,8 @@ export default { 'No bookmarked notes with id tags yet. Only classic (e-tag) bookmarks load in this feed.', 'No follows or relays to load yet.': 'No follows or relays to load yet.', 'Nothing to load for this feed.': 'Nothing to load for this feed.', + 'No posts loaded for this feed. Try refreshing.': + 'No posts loaded for this feed. Try refreshing.', 'Republish to ...': 'पुनः प्रकाशित करें...', 'All available relays': 'All available relays', 'All active relays (monitoring list)': 'All active relays (monitoring list)', diff --git a/src/i18n/locales/it.ts b/src/i18n/locales/it.ts index 47466886..ec9958d1 100644 --- a/src/i18n/locales/it.ts +++ b/src/i18n/locales/it.ts @@ -584,6 +584,8 @@ export default { 'No bookmarked notes with id tags yet. Only classic (e-tag) bookmarks load in this feed.', 'No follows or relays to load yet.': 'No follows or relays to load yet.', 'Nothing to load for this feed.': 'Nothing to load for this feed.', + 'No posts loaded for this feed. Try refreshing.': + 'No posts loaded for this feed. Try refreshing.', 'Republish to ...': 'Ripubblica a...', 'All available relays': 'All available relays', 'All active relays (monitoring list)': 'All active relays (monitoring list)', diff --git a/src/i18n/locales/ja.ts b/src/i18n/locales/ja.ts index ad38418e..23015099 100644 --- a/src/i18n/locales/ja.ts +++ b/src/i18n/locales/ja.ts @@ -581,6 +581,8 @@ export default { 'No bookmarked notes with id tags yet. Only classic (e-tag) bookmarks load in this feed.', 'No follows or relays to load yet.': 'No follows or relays to load yet.', 'Nothing to load for this feed.': 'Nothing to load for this feed.', + 'No posts loaded for this feed. Try refreshing.': + 'No posts loaded for this feed. Try refreshing.', 'Republish to ...': '再公開先 ...', 'All available relays': 'All available relays', 'All active relays (monitoring list)': 'All active relays (monitoring list)', diff --git a/src/i18n/locales/ko.ts b/src/i18n/locales/ko.ts index a23f8bfc..54ec58c6 100644 --- a/src/i18n/locales/ko.ts +++ b/src/i18n/locales/ko.ts @@ -579,6 +579,8 @@ export default { 'No bookmarked notes with id tags yet. Only classic (e-tag) bookmarks load in this feed.', 'No follows or relays to load yet.': 'No follows or relays to load yet.', 'Nothing to load for this feed.': 'Nothing to load for this feed.', + 'No posts loaded for this feed. Try refreshing.': + 'No posts loaded for this feed. Try refreshing.', 'Republish to ...': '다시 게시 ...', 'All available relays': 'All available relays', 'All active relays (monitoring list)': 'All active relays (monitoring list)', diff --git a/src/i18n/locales/pl.ts b/src/i18n/locales/pl.ts index 277a573a..c2ab1e49 100644 --- a/src/i18n/locales/pl.ts +++ b/src/i18n/locales/pl.ts @@ -582,6 +582,8 @@ export default { 'No bookmarked notes with id tags yet. Only classic (e-tag) bookmarks load in this feed.', 'No follows or relays to load yet.': 'No follows or relays to load yet.', 'Nothing to load for this feed.': 'Nothing to load for this feed.', + 'No posts loaded for this feed. Try refreshing.': + 'No posts loaded for this feed. Try refreshing.', 'Republish to ...': 'Przekaż ponownie do ...', 'All available relays': 'All available relays', 'All active relays (monitoring list)': 'All active relays (monitoring list)', diff --git a/src/i18n/locales/pt-BR.ts b/src/i18n/locales/pt-BR.ts index 05550545..6a149f5a 100644 --- a/src/i18n/locales/pt-BR.ts +++ b/src/i18n/locales/pt-BR.ts @@ -583,6 +583,8 @@ export default { 'No bookmarked notes with id tags yet. Only classic (e-tag) bookmarks load in this feed.', 'No follows or relays to load yet.': 'No follows or relays to load yet.', 'Nothing to load for this feed.': 'Nothing to load for this feed.', + 'No posts loaded for this feed. Try refreshing.': + 'No posts loaded for this feed. Try refreshing.', 'Republish to ...': 'Republicar em ...', 'All available relays': 'All available relays', 'All active relays (monitoring list)': 'All active relays (monitoring list)', diff --git a/src/i18n/locales/pt-PT.ts b/src/i18n/locales/pt-PT.ts index def61094..74e68cc4 100644 --- a/src/i18n/locales/pt-PT.ts +++ b/src/i18n/locales/pt-PT.ts @@ -583,6 +583,8 @@ export default { 'No bookmarked notes with id tags yet. Only classic (e-tag) bookmarks load in this feed.', 'No follows or relays to load yet.': 'No follows or relays to load yet.', 'Nothing to load for this feed.': 'Nothing to load for this feed.', + 'No posts loaded for this feed. Try refreshing.': + 'No posts loaded for this feed. Try refreshing.', 'Republish to ...': 'Transmitir para...', 'All available relays': 'All available relays', 'All active relays (monitoring list)': 'All active relays (monitoring list)', diff --git a/src/i18n/locales/ru.ts b/src/i18n/locales/ru.ts index 69982869..9b6e3335 100644 --- a/src/i18n/locales/ru.ts +++ b/src/i18n/locales/ru.ts @@ -583,6 +583,8 @@ export default { 'No bookmarked notes with id tags yet. Only classic (e-tag) bookmarks load in this feed.', 'No follows or relays to load yet.': 'No follows or relays to load yet.', 'Nothing to load for this feed.': 'Nothing to load for this feed.', + 'No posts loaded for this feed. Try refreshing.': + 'No posts loaded for this feed. Try refreshing.', 'Republish to ...': 'Ретранслировать в ...', 'All available relays': 'All available relays', 'All active relays (monitoring list)': 'All active relays (monitoring list)', diff --git a/src/i18n/locales/th.ts b/src/i18n/locales/th.ts index b97a8194..6114fa27 100644 --- a/src/i18n/locales/th.ts +++ b/src/i18n/locales/th.ts @@ -579,6 +579,8 @@ export default { 'No bookmarked notes with id tags yet. Only classic (e-tag) bookmarks load in this feed.', 'No follows or relays to load yet.': 'No follows or relays to load yet.', 'Nothing to load for this feed.': 'Nothing to load for this feed.', + 'No posts loaded for this feed. Try refreshing.': + 'No posts loaded for this feed. Try refreshing.', 'Republish to ...': 'เผยแพร่ซ้ำไปยัง ...', 'All available relays': 'All available relays', 'All active relays (monitoring list)': 'All active relays (monitoring list)', diff --git a/src/i18n/locales/zh.ts b/src/i18n/locales/zh.ts index 923b006a..39308ba0 100644 --- a/src/i18n/locales/zh.ts +++ b/src/i18n/locales/zh.ts @@ -577,6 +577,8 @@ export default { 'No bookmarked notes with id tags yet. Only classic (e-tag) bookmarks load in this feed.', 'No follows or relays to load yet.': 'No follows or relays to load yet.', 'Nothing to load for this feed.': 'Nothing to load for this feed.', + 'No posts loaded for this feed. Try refreshing.': + 'No posts loaded for this feed. Try refreshing.', 'Republish to ...': '重新发布到 ...', 'All available relays': 'All available relays', 'All active relays (monitoring list)': 'All active relays (monitoring list)', diff --git a/src/pages/primary/SpellsPage/fauxSpellFeeds.ts b/src/pages/primary/SpellsPage/fauxSpellFeeds.ts index 4b2b1c12..98638163 100644 --- a/src/pages/primary/SpellsPage/fauxSpellFeeds.ts +++ b/src/pages/primary/SpellsPage/fauxSpellFeeds.ts @@ -1,6 +1,7 @@ /** - * Built-in “faux spells”: same NoteList + filters as kind-777 spells; except Following, feeds use one-shot - * `fetchEvents` per subRequest (see NoteList `oneShotFetch`) instead of a live timeline subscription. + * Built-in “faux spells”: same NoteList + filters as kind-777 spells. The Spells page uses live + * `subscribeTimeline` (same as Following) so the first relay results stream in immediately instead of + * waiting for every relay to EOSE on a one-shot query. */ import { ExtendedKind, PROFILE_FEED_KINDS, READ_ONLY_RELAY_URLS } from '@/constants' import { diff --git a/src/pages/primary/SpellsPage/index.tsx b/src/pages/primary/SpellsPage/index.tsx index d463168b..02bbb1cd 100644 --- a/src/pages/primary/SpellsPage/index.tsx +++ b/src/pages/primary/SpellsPage/index.tsx @@ -402,7 +402,7 @@ const SpellsPage = forwardRef(function SpellsPage( if (spellProp?.trim()) { if (typeof requestIdleCallback !== 'undefined') { - idleId = requestIdleCallback(run, { timeout: 2500 }) + idleId = requestIdleCallback(run, { timeout: 400 }) } else { timeoutId = setTimeout(run, 0) } @@ -484,8 +484,8 @@ const SpellsPage = forwardRef(function SpellsPage( let catalogSyncDone = false - /** Defer catalog REQ so faux/kind-777 feed opens sockets and paints first. */ - const catalogDelayMs = 800 + /** Catalog sync runs in parallel with the open feed; avoid an artificial delay. */ + const catalogDelayMs = 0 if (cancelled) return delayId = setTimeout(() => { if (cancelled) return @@ -683,21 +683,25 @@ const SpellsPage = forwardRef(function SpellsPage( return [{ urls, filter: buildDiscussionFilter() }] } if (selectedFauxSpell === 'media') { - if (!feedUrls.length) return [] - return [{ urls: feedUrls, filter: buildMediaSpellFilter() }] + const urls = appendCuratedReadOnlyRelays(feedUrls, blockedRelays) + if (!urls.length) return [] + return [{ urls, filter: buildMediaSpellFilter() }] } if (selectedFauxSpell === 'calendar') { - if (!feedUrls.length) return [] - return [{ urls: feedUrls, filter: buildCalendarSpellFilter() }] + const urls = appendCuratedReadOnlyRelays(feedUrls, blockedRelays) + if (!urls.length) return [] + return [{ urls, filter: buildCalendarSpellFilter() }] } if (selectedFauxSpell === 'interests') { if (!pubkey || !interestListEvent) return [] const topics = interestListEvent.tags.filter((tag) => tag[0] === 't' && tag[1]).map((tag) => tag[1]!) - return buildInterestsSubRequests(feedUrls, topics, PROFILE_FEED_KINDS) + const urls = appendCuratedReadOnlyRelays(feedUrls, blockedRelays) + return buildInterestsSubRequests(urls, topics, PROFILE_FEED_KINDS) } if (selectedFauxSpell === 'bookmarks') { if (!pubkey) return [] - return buildBookmarksSubRequests(bookmarkListEvent, feedUrls) + const urls = appendCuratedReadOnlyRelays(feedUrls, blockedRelays) + return buildBookmarksSubRequests(bookmarkListEvent, urls) } if (selectedFauxSpell === 'followPacks') { const urls = appendCuratedReadOnlyRelays(feedUrls, blockedRelays) @@ -1331,7 +1335,7 @@ const SpellsPage = forwardRef(function SpellsPage( spellFeedInstrumentToken={spellFeedInstrumentToken} onSpellFeedFirstPaint={handleSpellFeedFirstPaint} useFilterAsIs={fauxNoteListUseFilterAsIs} - oneShotFetch={selectedFauxSpell !== 'following'} + oneShotFetch={false} showKind1OPs={selectedFauxSpell === 'following' ? showKind1OPs : true} showKind1Replies={selectedFauxSpell === 'following' ? showKind1Replies : true} showKind1111={selectedFauxSpell === 'following' ? showKind1111 : true} diff --git a/src/services/client.service.ts b/src/services/client.service.ts index e6722d3d..b659d35c 100644 --- a/src/services/client.service.ts +++ b/src/services/client.service.ts @@ -1045,10 +1045,7 @@ class ClientService extends EventTarget { }: { startLogin?: () => void needSort?: boolean - /** - * Ignored by {@link ClientService.subscribeTimeline} (kept for compatibility). Initial completion is - * aggregate relay EOSE only; per-event results stream via `onEvents` without faking EOSE. - */ + /** Passed to each shard’s {@link ClientService._subscribeTimeline}: 2s after first event completes initial load if EOSE is slower. */ firstRelayResultGraceMs?: number } = {} ) { @@ -1513,8 +1510,12 @@ class ClientService extends EventTarget { { startLogin, needSort = true, - /** @deprecated No longer used; streaming does not fake EOSE (see flushStreamingSnapshot). Kept for call-site compatibility. */ - firstRelayResultGraceMs: _unusedFirstRelayGraceMs = FIRST_RELAY_RESULT_GRACE_MS, + /** + * After the **first** stored event arrives from any relay, wait this long then treat the initial + * backlog as complete (same as aggregate EOSE): enables pagination + live `onNew` without waiting for + * every slow/hung relay. Real EOSE still clears the timer and completes earlier if all relays finish first. + */ + firstRelayResultGraceMs = FIRST_RELAY_RESULT_GRACE_MS, relayReqLog }: { startLogin?: () => void @@ -1524,7 +1525,6 @@ class ClientService extends EventTarget { relayReqLog?: { groupId: string } } = {} ) { - void _unusedFirstRelayGraceMs const relays = Array.from(new Set(urls)) const key = this.generateTimelineKey(relays, filter) let timeline = this.timelines[key] @@ -1543,11 +1543,28 @@ class ClientService extends EventTarget { let events: NEvent[] = [] let eosedAt: number | null = null + let firstResultGraceTimer: ReturnType | null = null + const clearFirstResultGraceTimer = () => { + if (firstResultGraceTimer != null) { + clearTimeout(firstResultGraceTimer) + firstResultGraceTimer = null + } + } + const armFirstResultGraceAfterFirstEvent = () => { + if (eosedAt != null || firstResultGraceTimer != null) return + if (events.length === 0) return + if (firstRelayResultGraceMs <= 0) return + firstResultGraceTimer = setTimeout(() => { + firstResultGraceTimer = null + if (eosedAt == null) { + handleTimelineEose(true) + } + }, firstRelayResultGraceMs) + } + /** - * Stream every matching event to the UI immediately. Do **not** use a "grace EOSE" timer: it set `eosedAt` - * to wall-clock time while relays were still returning historical rows, so `evt.created_at > eosedAt` was - * almost always false and later relay results were dropped until the feed looked empty/slow. - * Real initial completion is only when {@link ClientService.subscribe} fires aggregate `oneose` (all relays). + * Stream matching events to the UI immediately. Initial completion is either aggregate `oneose` from all + * relays, or {@link firstRelayResultGraceMs} after the first event (whichever comes first). */ let streamFlushMicrotask = false const flushStreamingSnapshot = () => { @@ -1577,6 +1594,8 @@ class ClientService extends EventTarget { if (!eosed) return if (eosedAt != null) return + clearFirstResultGraceTimer() + eosedAt = dayjs().unix() if (!needSort) { @@ -1614,6 +1633,7 @@ class ClientService extends EventTarget { if (!eosedAt) { events.push(evt) flushStreamingSnapshot() + armFirstResultGraceAfterFirstEvent() return } // new event @@ -1659,6 +1679,7 @@ class ClientService extends EventTarget { return { timelineKey: key, closer: () => { + clearFirstResultGraceTimer() onEvents = () => {} onNew = () => {} subCloser.close()