feat(feed/chat): lazy-render + pagination for long scrolls

Server pagination
  - blockchain.PostsByAuthor signature extended with beforeTs int64;
    passing 0 keeps the previous "everything, newest first" behaviour,
    non-zero skips posts with CreatedAt >= beforeTs so clients can
    paginate older results.
  - node.FeedConfig.PostsByAuthor callback type updated; the two
    /feed endpoints that use it (timeline + author) now accept
    `?before=<unix_seconds>` and forward it through. /feed/author
    limit default dropped from 50 to 30 to match the client's page
    size.
  - node/api_common.go: new queryInt64 helper for parsing the cursor
    param safely (matches the queryInt pattern already used).

Client infinite scroll (Feed tab)
  - lib/feed.ts: fetchTimeline / fetchAuthorPosts accept
    `{limit?, before?}` options. Old signatures still work for other
    callers (fetchForYou / fetchTrending / fetchHashtag) — those are
    ranked feeds that don't have a stable cursor so they stay
    single-shot.
  - feed/index.tsx: tracks loadingMore / exhausted state. onEndReached
    (threshold 0.6) fires loadMore() which fetches the next 20 posts
    using the oldest currently-loaded post's created_at as `before`.
    Deduplicates on post_id before appending. Stops when the server
    returns < PAGE_SIZE items. ListFooterComponent shows a small
    spinner during paginated fetches.
  - FlatList lazy-render tuning on all feed lists (index + hashtag):
    initialNumToRender:10, maxToRenderPerBatch:8, windowSize:7,
    removeClippedSubviews — first paint stays quick even with 100+
    posts loaded.

Chat lazy render
  - chats/[id].tsx FlatList: initialNumToRender:25 (~1.5 screens),
    maxToRenderPerBatch:12, windowSize:10, removeClippedSubviews.
    Keeps initial chat open snappy on conversations with thousands
    of messages; RN re-renders a small window around the viewport
    and drops the rest.

Tests
  - chain_test.go updated for new PostsByAuthor signature.
  - All 7 Go packages green.
  - tsc --noEmit clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
vsecoder
2026-04-18 21:51:43 +03:00
parent 1c6622e809
commit 6425b5cffb
8 changed files with 137 additions and 14 deletions

View File

@@ -226,13 +226,24 @@ export async function bumpView(postID: string): Promise<void> {
} catch { /* ignore */ }
}
export async function fetchAuthorPosts(pub: string, limit = 30): Promise<FeedPostItem[]> {
const resp = await getJSON<TimelineResponse>(`/feed/author/${pub}?limit=${limit}`);
export async function fetchAuthorPosts(
pub: string, opts: { limit?: number; before?: number } = {},
): Promise<FeedPostItem[]> {
const limit = opts.limit ?? 30;
const qs = opts.before
? `?limit=${limit}&before=${opts.before}`
: `?limit=${limit}`;
const resp = await getJSON<TimelineResponse>(`/feed/author/${pub}${qs}`);
return resp.posts ?? [];
}
export async function fetchTimeline(followerPub: string, limit = 30): Promise<FeedPostItem[]> {
const resp = await getJSON<TimelineResponse>(`/feed/timeline?follower=${followerPub}&limit=${limit}`);
export async function fetchTimeline(
followerPub: string, opts: { limit?: number; before?: number } = {},
): Promise<FeedPostItem[]> {
const limit = opts.limit ?? 30;
let qs = `?follower=${followerPub}&limit=${limit}`;
if (opts.before) qs += `&before=${opts.before}`;
const resp = await getJSON<TimelineResponse>(`/feed/timeline${qs}`);
return resp.posts ?? [];
}