Keep a profile pinned note out of the feed store so it renders once (#499)

This commit is contained in:
Coracle-Bot 2026-09-11 00:47:26 +00:00 committed by hodlbod
parent ad480ad4a0
commit 5520b851de
2 changed files with 16 additions and 5 deletions

View file

@ -465,9 +465,11 @@ test("US-079 read a person's notes", async ({seed, as}) => {
makeEvent(NOTE, {content: "Anyone seen the tide charts?", created_at: at(4, HOUR)}), makeEvent(NOTE, {content: "Anyone seen the tide charts?", created_at: at(4, HOUR)}),
) )
// Older than the feed's first window, so the pin is fetched by id while the feed only
// reaches it by paging — the order in which one note arrives down both routes.
pinned = space.event( pinned = space.event(
user.alice, user.alice,
makeEvent(NOTE, {content: "PINNED how to read a tide chart", created_at: at(3, HOUR)}), makeEvent(NOTE, {content: "PINNED how to read a tide chart", created_at: at(2, MONTH)}),
) )
space.event( space.event(
@ -521,6 +523,7 @@ test("US-079 read a person's notes", async ({seed, as}) => {
// The pin outranks the newer notes; the rest are newest first. // The pin outranks the newer notes; the rest are newest first.
await expect(list.first()).toContainText("PINNED") await expect(list.first()).toContainText("PINNED")
await expect(list.filter({hasText: "PINNED"})).toHaveCount(1)
await expect(list.nth(1)).toContainText("NEWEST") await expect(list.nth(1)).toContainText("NEWEST")
await expect(list.nth(2)).toContainText("MIDDLE") await expect(list.nth(2)).toContainText("MIDDLE")

View file

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import {onDestroy, onMount} from "svelte" import {onDestroy, onMount} from "svelte"
import {derived, writable} from "svelte/store" import {derived, writable} from "svelte/store"
import type {Writable} from "svelte/store" import type {Readable} from "svelte/store"
import {sortBy, uniqBy, now} from "@welshman/lib" import {sortBy, uniqBy, now} from "@welshman/lib"
import type {Maybe} from "@welshman/lib" import type {Maybe} from "@welshman/lib"
import {NOTE, outbox} from "@welshman/util" import {NOTE, outbox} from "@welshman/util"
@ -27,6 +27,11 @@
const pinnedIds = derived($app.use(PinLists).one(pubkey), $pinList => $pinList?.ids() ?? []) const pinnedIds = derived($app.use(PinLists).one(pubkey), $pinList => $pinList?.ids() ?? [])
// Pinned notes are asked for by id instead of by the feed's filters, so they get a store of
// their own. A feed dedupes and sorts what it holds as it loads it, and writing to its store
// from out here bypasses both.
const pinnedEvents = writable<TrustedEvent[]>([])
$effect(() => { $effect(() => {
if ($pinnedIds.length > 0) { if ($pinnedIds.length > 0) {
const controller = new AbortController() const controller = new AbortController()
@ -36,7 +41,8 @@
relays: $relays, relays: $relays,
filters: [{ids: $pinnedIds}], filters: [{ids: $pinnedIds}],
signal: controller.signal, signal: controller.signal,
onEvent: e => events.update($events => uniqBy(e => e.id, $events.concat(e))), onEvent: event =>
pinnedEvents.update($pinned => uniqBy(e => e.id, $pinned.concat(event))),
}), }),
) )
@ -49,12 +55,14 @@
const exhausted = $derived($older?.status === "exhausted") const exhausted = $derived($older?.status === "exhausted")
const loading = $derived(isFeedLoading($older)) const loading = $derived(isFeedLoading($older))
let events: Writable<TrustedEvent[]> = $state(writable([])) let events: Readable<TrustedEvent[]> = $state(writable([]))
const feedEvents = $derived( const feedEvents = $derived(
sortBy( sortBy(
e => ($pinnedIds.includes(e.id) ? -(now() + e.created_at) : -e.created_at), e => ($pinnedIds.includes(e.id) ? -(now() + e.created_at) : -e.created_at),
$events.filter(e => getReplyTags(e.tags).replies.length === 0), uniqBy(e => e.id, [...$pinnedEvents, ...$events]).filter(
e => getReplyTags(e.tags).replies.length === 0,
),
), ),
) )