fix some feed loading bugs

This commit is contained in:
Jon Staab 2026-06-29 16:28:11 -07:00
parent 10119aaa6d
commit 77ac59008b
11 changed files with 69 additions and 44 deletions

View file

@ -10,6 +10,7 @@
follow,
unfollow,
tagPubkey,
loadRelayList,
} from "@welshman/app"
import {compressFile} from "@lib/html"
import Copy from "@assets/icons/copy.svg?dataurl"
@ -24,6 +25,7 @@
import Icon from "@lib/components/Icon.svelte"
import Link from "@lib/components/Link.svelte"
import Button from "@lib/components/Button.svelte"
import Spinner from "@lib/components/Spinner.svelte"
import Popover from "@lib/components/Popover.svelte"
import ProfileCircle from "@app/components/ProfileCircle.svelte"
import ProfileTrust from "@app/components/ProfileTrust.svelte"
@ -254,7 +256,14 @@
<ProfileTrust pubkey={target} {isSelf} />
<ProfileSharedSpaces pubkey={target} {isSelf} />
</div>
<ProfilePageNotes pubkey={target} />
{#await loadRelayList(target)}
<p class="my-12 flex items-center justify-center gap-2">
<Spinner loading />
Loading notes...
</p>
{:then}
<ProfilePageNotes pubkey={target} />
{/await}
</div>
</div>
</div>

View file

@ -2,7 +2,7 @@
import {onMount} from "svelte"
import {derived, writable} from "svelte/store"
import type {Writable} from "svelte/store"
import {sortBy, now} from "@welshman/lib"
import {sortBy, uniqBy, now} from "@welshman/lib"
import {NOTE, getReplyTags, getListTags, getEventTagValues} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util"
import {derivePinList} from "@welshman/app"
@ -30,7 +30,7 @@
relays: Router.get().FromPubkeys([pubkey]).getUrls(),
filters: [{ids: $pinnedIds}],
signal: controller.signal,
onEvent: e => events.update($events => $events.concat(e)),
onEvent: e => events.update($events => uniqBy(e => e.id, $events.concat(e))),
})
return () => controller.abort()
@ -50,7 +50,7 @@
onMount(() => {
const feed = makeFeed({
url: Router.get().FromPubkeys([pubkey]).getUrls()[0],
relays: Router.get().FromPubkeys([pubkey]).getUrls(),
element: element!,
filters: [{kinds: [NOTE], authors: [pubkey]}],
onBackwardExhausted: () => {
@ -75,8 +75,9 @@
{/if}
{/each}
{#if !exhausted}
<p class="my-12 flex items-center justify-center">
<p class="my-12 flex items-center justify-center gap-2">
<Spinner loading />
Loading notes...
</p>
{/if}
</div>

View file

@ -18,6 +18,7 @@ import {
now,
on,
sortBy,
uniqBy,
MONTH,
YEAR,
} from "@welshman/lib"
@ -105,14 +106,14 @@ export const userFeedFavorites = makeUserData(feedFavoritesByPubkey, loadFeedFav
export const loadUserFeedFavorites = makeUserLoader(loadFeedFavorites)
export const makeFeed = ({
url,
relays,
filters,
element,
onBackwardExhausted,
onForwardExhausted,
at = now(),
}: {
url: string
relays: string[]
filters: Filter[]
element: HTMLElement
onBackwardExhausted?: () => void
@ -121,9 +122,15 @@ export const makeFeed = ({
}) => {
const controller = new AbortController()
const events = writable<TrustedEvent[]>([])
const seen = new Set<string>()
let interval = int(MONTH)
let buffer = sortEventsDesc(getEventsForUrl(url, filters))
let buffer = sortEventsDesc(
uniqBy(
e => e.id,
relays.flatMap(url => Array.from(getEventsForUrl(url, filters))),
),
)
let backwardWindow = [at - interval, at]
let forwardWindow = [at, at + interval]
@ -142,6 +149,12 @@ export const makeFeed = ({
const visible: TrustedEvent[] = []
for (const event of newEvents) {
if (seen.has(event.id)) {
continue
}
seen.add(event.id)
if (between([backwardWindow[0], forwardWindow[1]], event.created_at)) {
visible.push(event)
} else {
@ -164,6 +177,7 @@ export const makeFeed = ({
$events = [...$events, event]
}
}
return $events
})
}
@ -182,7 +196,9 @@ export const makeFeed = ({
}
const matching = added.filter(
event => matchFilters(filters, event) && tracker.getRelays(event.id).has(url),
event =>
matchFilters(filters, event) &&
relays.some(url => tracker.getRelays(event.id).has(url)),
)
if (matching.length > 0) {
@ -190,8 +206,8 @@ export const makeFeed = ({
}
}),
),
on(tracker, "add", (id: string, trackerUrl: string) => {
if (trackerUrl === url) {
on(tracker, "add", (id: string, url: string) => {
if (relays.includes(url)) {
const event = repository.getEvent(id)
if (event && matchFilters(filters, event)) {
@ -203,7 +219,7 @@ export const makeFeed = ({
const loadTimeframe = async (since: number, until: number) => {
const events = await request({
relays: [url],
relays,
autoClose: true,
signal: controller.signal,
filters: filters.map(filter => ({...filter, since, until})),
@ -270,18 +286,19 @@ export const makeFeed = ({
}
export const makeCalendarFeed = ({
url,
relays,
filters,
element,
onExhausted,
}: {
url: string
relays: string[]
filters: Filter[]
element: HTMLElement
onExhausted?: () => void
}) => {
const interval = int(5, MONTH)
const controller = new AbortController()
const seen = new Set<string>()
let exhaustedScrollers = 0
let backwardWindow = [now() - interval, now()]
@ -291,15 +308,26 @@ export const makeCalendarFeed = ({
const getEnd = (event: TrustedEvent) => parseInt(getTagValue("end", event.tags) || "")
const events = writable(sortBy(getStart, getEventsForUrl(url, filters)))
const events = writable(
sortBy(
getStart,
uniqBy(
e => e.id,
relays.flatMap(url => Array.from(getEventsForUrl(url, filters))),
),
),
)
// Batch-insert calendar events into the store with a single update
const insertEvents = (newEvents: TrustedEvent[]) => {
const valid = newEvents.filter(e => !isNaN(getStart(e)) && !isNaN(getEnd(e)))
const valid = newEvents.filter(e => !isNaN(getStart(e)) && !isNaN(getEnd(e)) && !seen.has(e.id))
if (valid.length === 0) return
events.update($events => {
for (const event of valid) {
seen.add(event.id)
const start = getStart(event)
const address = getAddress(event)
@ -320,6 +348,7 @@ export const makeCalendarFeed = ({
$events = [...$events.filter(e => getAddress(e) !== address), event]
}
}
return $events
})
}
@ -342,8 +371,8 @@ export const makeCalendarFeed = ({
}
}),
),
on(tracker, "add", (id: string, trackerUrl: string) => {
if (trackerUrl === url) {
on(tracker, "add", (id: string, url: string) => {
if (relays.includes(url)) {
const event = repository.getEvent(id)
if (event && matchFilters(filters, event)) {
@ -357,7 +386,7 @@ export const makeCalendarFeed = ({
const hashes = daysBetween(since, until).map(String)
request({
relays: [url],
relays,
autoClose: true,
signal: controller.signal,
filters: [{kinds: [EVENT_TIME], "#D": hashes}],

View file

@ -19,26 +19,18 @@
import {decodePubkey} from "@lib/util"
import Page from "@lib/components/Page.svelte"
import PageContent from "@lib/components/PageContent.svelte"
import Spinner from "@lib/components/Spinner.svelte"
import ProfilePage from "@app/components/ProfilePage.svelte"
import {loadGroupList} from "@app/groups"
const {npub} = $page.params as MakeNonOptional<typeof $page.params>
let pubkey = $state<string | undefined>()
let ready = $state(false)
const pubkey = decodePubkey(npub)
onMount(async () => {
const decoded = decodePubkey(npub)
if (!decoded) {
goto("/people", {replaceState: true})
return
if (!pubkey) {
return goto("/people", {replaceState: true})
}
pubkey = decoded
try {
await loadProfile(pubkey)
await loadRelayList(pubkey)
@ -66,8 +58,6 @@
relays: Router.get().FromPubkeys([pubkey]).getUrls(),
filters,
})
ready = true
} catch {
goto("/people", {replaceState: true})
}
@ -76,12 +66,8 @@
<Page>
<PageContent class="p-0 md:p-4">
{#if ready && pubkey}
{#if pubkey}
<ProfilePage {pubkey} />
{:else}
<p class="flex items-center justify-center py-20">
<Spinner loading />
</p>
{/if}
</PageContent>
</Page>

View file

@ -379,7 +379,7 @@
cleanup?.()
const feed = makeFeed({
url,
relays: [url],
at: at || now(),
element: element!,
filters: [{kinds: [MESSAGE, ROOM_ADD_MEMBER], "#h": [h]}],

View file

@ -95,7 +95,7 @@
onMount(() => {
const feed = makeCalendarFeed({
url,
relays: [url],
element: element!,
filters: [{kinds: [EVENT_TIME]}, makeCommentFilter([EVENT_TIME])],
onExhausted: () => {

View file

@ -260,7 +260,7 @@
cleanup?.()
const feed = makeFeed({
url,
relays: [url],
at: at || now(),
element: element!,
filters: [{kinds: [MESSAGE, RELAY_ADD_MEMBER]}],

View file

@ -46,7 +46,7 @@
onMount(() => {
const feed = makeFeed({
url,
relays: [url],
element: element!,
filters: [{kinds: [CLASSIFIED]}, makeCommentFilter([CLASSIFIED])],
onBackwardExhausted: () => {

View file

@ -46,7 +46,7 @@
onMount(() => {
const feed = makeFeed({
url,
relays: [url],
element: element!,
filters: [{kinds: [ZAP_GOAL]}, makeCommentFilter([ZAP_GOAL])],
onBackwardExhausted: () => {

View file

@ -46,7 +46,7 @@
onMount(() => {
const feed = makeFeed({
url,
relays: [url],
element: element!,
filters: [{kinds: [POLL]}, makeCommentFilter([POLL])],
onBackwardExhausted: () => {

View file

@ -52,7 +52,7 @@
onMount(() => {
const feed = makeFeed({
url,
relays: [url],
element: element!,
filters: [{kinds: [THREAD]}, makeCommentFilter([THREAD])],
onBackwardExhausted: () => {