feat(feed): resolve author names and pictures from kind-0 metadata

This commit is contained in:
Avi 2026-09-23 16:06:19 -05:00
commit a76d8dff4e
4 changed files with 193 additions and 5 deletions

View file

@ -142,6 +142,10 @@ export interface FeedItem {
author: string;
/** Bech32 `npub` of the author, for display. */
author_npub: string;
/** Author display name from their latest kind-0, when one was found. */
author_name?: string | null;
/** Author picture URL from their latest kind-0, when one was found. */
author_picture?: string | null;
content: string;
/** Unix timestamp the note was created. */
created_at: number;

View file

@ -219,12 +219,20 @@ export function FeedScreen({ onNavigate }: FeedScreenProps) {
<ul className="feed-list">
{items.map((item) => (
<li key={item.id} className="feed-item">
<Avatar npub={item.author_npub} label={shortenNpub(item.author_npub, shorten)} />
<Avatar
npub={item.author_npub}
label={item.author_name ?? shortenNpub(item.author_npub, shorten)}
picture={item.author_picture ?? null}
/>
<div className="feed-item-body">
<div className="feed-item-meta">
<span className="mono" title={item.author_npub}>
{shortenNpub(item.author_npub, shorten)}
</span>
{item.author_name ? (
<span title={item.author_npub}>{item.author_name}</span>
) : (
<span className="mono" title={item.author_npub}>
{shortenNpub(item.author_npub, shorten)}
</span>
)}
<span className="feed-item-time">{formatDate(item.created_at)}</span>
{item.relays.length > 1 && (
<Badge tone="neutral">{item.relays.length} relays</Badge>

View file

@ -22,6 +22,22 @@ describe('FeedScreen', () => {
expect(screen.getByText('2 relays')).toBeInTheDocument();
});
it('shows the author name and picture when the feed resolved them', async () => {
const backend = createFakeBackend();
backend.feedItems = backend.feedItems.map((item, index) =>
index === 0
? { ...item, author_name: 'Alice Liddell', author_picture: 'https://example.com/alice.png' }
: item,
);
renderFeed(backend);
renderWithApp(<FeedScreen onNavigate={vi.fn()} />);
expect(await screen.findByText('Alice Liddell')).toBeInTheDocument();
// The avatar image is decorative (empty alt), so query by src.
const avatar = document.querySelector('img[src="https://example.com/alice.png"]');
expect(avatar).not.toBeNull();
});
it('disable relays shows an empty state that can navigate to relays', async () => {
const settings = {
theme: 'light' as const,