Skip to content

fix(sdk): stop ranked-posts pagination at the end of the feed - #1324

Merged
feruzm merged 4 commits into
developfrom
bugfix/sdk-ranked-posts-pagination
Aug 3, 2026
Merged

fix(sdk): stop ranked-posts pagination at the end of the feed#1324
feruzm merged 4 commits into
developfrom
bugfix/sdk-ranked-posts-pagination

Conversation

@feruzm

@feruzm feruzm commented Aug 2, 2026

Copy link
Copy Markdown
Member

Found while reviewing #1322, which switches the self-hosted community feed onto this query. The bug is pre-existing and affects every community feed on the main app too.

getNextPageParam returned an object for every page, including an empty one:

getNextPageParam: (lastPage: Entry[]) => {
  const last = lastPage?.[lastPage.length - 1];
  return { author: last?.author, permlink: last?.permlink, hasNextPage: (lastPage?.length ?? 0) > 0 };
}

React Query reads "there is no next page" from undefined alone. A returned object means there is one, whatever it contains, so hasNextPage stayed true forever and the hasNextPage field inside the param was only ever read by the query function.

An infinite list therefore keeps calling fetchNextPage once the reader reaches the bottom. The query function does short-circuit on that stale flag, so no further RPCs go out, but each call still appends an empty page: the query state churns and the cache grows for as long as the reader sits at the end of the feed.

Returning undefined on an empty page ends it. Termination is on an empty page rather than a short one, since a short page is not reliably the end and one extra request that comes back empty is cheap.

584 SDK tests pass, typecheck clean. Restoring the old return fails the new test.

Related, not fixed here

The same function re-sorts every non-hot page by creation date before getNextPageParam picks the cursor, so for trending, payout and muted the cursor is the oldest post in the page rather than the last in the bridge ranked order. The next request then starts from the middle of the previous ranked page, which duplicates and skips posts while scrolling. Fixing that means deciding whether the date sort should apply to ranked sorts at all, which changes visible feed ordering on the main app, so it is filed separately as #1325 rather than folded in here.

Summary by CodeRabbit

  • Bug Fixes

    • Improved ranked-post pagination to maintain correct cursors when display ordering differs from backend ordering.
    • Ensured pinned posts and date-based sorting are applied consistently without disrupting pagination.
    • Pagination now stops correctly when no additional posts are available.
    • Preserved the correct final post as the cursor when more results remain.
    • Maintained accurate pagination when ranked and displayed post orders differ.
  • Chores

    • Updated SDK and wallet package releases with the latest fixes.

getNextPageParam returned an object for every page, including an empty
one. React Query reads "there is no next page" from undefined alone, so
hasNextPage stayed true forever.

An infinite list therefore keeps calling fetchNextPage once the reader
reaches the bottom. The query function short-circuits on the stale
hasNextPage flag in the page param, so no further RPCs go out, but each
call still appends an empty page: the query state churns and the cache
grows for as long as the reader sits there.

Returning undefined on an empty page ends it. Restoring the old return
fails the new test.
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 19b48317-a7d6-4bd6-bea6-53d0bd67c4ac

📥 Commits

Reviewing files that changed from the base of the PR and between d4f75e7 and adfe7d6.

⛔ Files ignored due to path filters (6)
  • packages/sdk/dist/browser/index.js is excluded by !**/dist/**
  • packages/sdk/dist/browser/index.js.map is excluded by !**/dist/**, !**/*.map
  • packages/sdk/dist/node/index.cjs is excluded by !**/dist/**
  • packages/sdk/dist/node/index.cjs.map is excluded by !**/dist/**, !**/*.map
  • packages/sdk/dist/node/index.mjs is excluded by !**/dist/**
  • packages/sdk/dist/node/index.mjs.map is excluded by !**/dist/**, !**/*.map
📒 Files selected for processing (4)
  • packages/sdk/CHANGELOG.md
  • packages/sdk/package.json
  • packages/wallets/CHANGELOG.md
  • packages/wallets/package.json

📝 Walkthrough

Walkthrough

Ranked post queries preserve bridge response order for pagination cursors. Display ordering runs separately through React Query’s select. Empty pages terminate pagination. Tests cover cursor and ordering behavior. SDK and wallets versions were updated.

Changes

Ranked pagination behavior

Layer / File(s) Summary
Query ordering and pagination
packages/sdk/src/modules/posts/queries/get-posts-ranked-query-options.ts
orderForDisplay preserves pinned entries and applies display sorting after query execution. Pagination uses the final bridge-order entry and stops on empty pages.
Pagination regression coverage
packages/sdk/src/modules/posts/queries/get-posts-ranked-query-options.spec.ts
Tests verify bridge ordering, empty-page termination, final-entry cursors, and differing ranked and display orders.
SDK and wallets release metadata
packages/sdk/package.json, packages/sdk/CHANGELOG.md, packages/wallets/package.json, packages/wallets/CHANGELOG.md
The SDK version changed to 2.3.72. The wallets version changed to 5.0.72 and records the SDK update.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • ecency/vision-next#1183: Both changes modify ranked-post query pagination typing or behavior in get-posts-ranked-query-options.ts.

Sequence Diagram(s)

sequenceDiagram
  participant BridgeAPI
  participant QueryFunction
  participant ReactQuery
  BridgeAPI->>QueryFunction: Return ranked posts in bridge order
  QueryFunction->>ReactQuery: Return filtered page and bridge-order cursor
  ReactQuery->>ReactQuery: Apply orderForDisplay
Loading

Poem

I’m a rabbit guarding each page in line,
Bridge-order cursors stay correct and fine.
Pinned posts hop first, the rest sort with care,
Empty pages show that no posts are there.
The next cursor waits with a twitch of my ear.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the primary change: stopping ranked-posts pagination when the feed ends.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bugfix/sdk-ranked-posts-pagination

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

feruzm added 2 commits August 3, 2026 13:07
The termination tests were written into the spec file rather than added to
it, dropping ten existing cases: null and malformed responses, the
created-desc sort, pinned ordering and multi-pin retention, the hot
exception, network error propagation and the finite query. All are
restored and the two new cases sit alongside them.
getNextPageParam reads the cursor from the last entry of the page the
query function returns, and the bridge continues a ranked feed from that
entry in ITS ranking. The page was re-sorted by creation date first, so
for trending, payout and muted the cursor named the oldest entry by date
rather than the last by rank: the next request started from the middle of
the previous ranked page, and scrolling repeated some posts and skipped
others.

The display ordering, pinned entries first then created descending, moves
into `select`, which React Query applies after pagination. Nothing a
reader sees changes; the cursor is now taken from the bridge's own order.

This closes the second half of the ranked-posts problem, so #1322 can move
community feeds onto this query without inheriting it.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/sdk/src/modules/posts/queries/get-posts-ranked-query-options.ts (2)

104-125: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

pageParam.hasNextPage is now vestigial.

getNextPageParam only returns undefined (Line 117) or an object with hasNextPage: true (Line 123). It never produces hasNextPage: false. Combined with initialPageParam.hasNextPage: true at Line 107, pageParam.hasNextPage is always true whenever queryFn runs. The early-return guard at Line 62 (if (!pageParam.hasNextPage) return [];) is now unreachable dead code.

Leaving this field in place can mislead a future maintainer into believing that toggling hasNextPage on the cursor object controls pagination, when only the undefined vs. object return value of getNextPageParam actually does. Remove the field and the dead guard, or document why it is intentionally retained.

♻️ Proposed cleanup
-    queryFn: async ({ pageParam, signal }: { pageParam: PageParam; signal: AbortSignal }) => {
-      if (!pageParam.hasNextPage) {
-        return [];
-      }
-
-      let sanitizedTag = tag;
+    queryFn: async ({ pageParam, signal }: { pageParam: PageParam; signal: AbortSignal }) => {
+      let sanitizedTag = tag;
@@
     initialPageParam: {
       author: undefined,
       permlink: undefined,
-      hasNextPage: true,
     } as PageParam,
     getNextPageParam: (lastPage: Entry[]) => {
       const last = lastPage?.[lastPage.length - 1];
       if (!last) {
         return undefined;
       }
 
       return {
         author: last.author,
         permlink: last.permlink,
-        hasNextPage: true,
       };
     },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/sdk/src/modules/posts/queries/get-posts-ranked-query-options.ts`
around lines 104 - 125, Remove the vestigial pageParam.hasNextPage field from
initialPageParam and the object returned by getNextPageParam, then delete the
unreachable early-return guard in the query function. Preserve pagination
termination through getNextPageParam returning undefined when the last page is
empty.

99-102: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

select re-sorts every loaded page on each fetch.

data.pages.map((page) => orderForDisplay(page, sort)) runs orderForDisplay (which itself does a filter + sort) over every page in data.pages, not just the newest one, each time select runs. As a reader scrolls a community feed and accumulates more pages, this becomes repeated O(n log n) work over already-ordered pages, and the cost compounds across the scrolling session.

Cache per-page results keyed by page reference so unchanged pages are not re-sorted.

⚡ Proposed memoization
 export function getPostsRankedInfiniteQueryOptions(
   sort: string,
   tag: string,
   limit = 20,
   observer = "",
   enabled = true,
   _options: GetPostsRankedOptions = {}
 ) {
+  const displayOrderCache = new WeakMap<Entry[], Entry[]>();
+
   return infiniteQueryOptions<
     Entry[],
     Error,
     InfiniteData<Entry[], PageParam>,
     (string | number)[],
     PageParam
   >({
     queryKey: QueryKeys.posts.postsRanked(sort, tag, limit, observer),
     queryFn: async (...
     select: (data) => ({
       ...data,
-      pages: data.pages.map((page) => orderForDisplay(page, sort)),
+      pages: data.pages.map((page) => {
+        const cached = displayOrderCache.get(page);
+        if (cached) return cached;
+        const ordered = orderForDisplay(page, sort);
+        displayOrderCache.set(page, ordered);
+        return ordered;
+      }),
     }),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/sdk/src/modules/posts/queries/get-posts-ranked-query-options.ts`
around lines 99 - 102, Update the select transformation in the ranked-post query
options to memoize orderForDisplay results per page reference, keyed by the page
object and sort value. Reuse cached ordering for unchanged pages and only invoke
orderForDisplay for new or changed pages while preserving the existing pages
output shape.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/sdk/src/modules/posts/queries/get-posts-ranked-query-options.ts`:
- Around line 104-125: Remove the vestigial pageParam.hasNextPage field from
initialPageParam and the object returned by getNextPageParam, then delete the
unreachable early-return guard in the query function. Preserve pagination
termination through getNextPageParam returning undefined when the last page is
empty.
- Around line 99-102: Update the select transformation in the ranked-post query
options to memoize orderForDisplay results per page reference, keyed by the page
object and sort value. Reuse cached ordering for unchanged pages and only invoke
orderForDisplay for new or changed pages while preserving the existing pages
output shape.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1c5eeba8-2900-40cc-903e-030ccecf68d2

📥 Commits

Reviewing files that changed from the base of the PR and between 39aef11 and d4f75e7.

📒 Files selected for processing (2)
  • packages/sdk/src/modules/posts/queries/get-posts-ranked-query-options.spec.ts
  • packages/sdk/src/modules/posts/queries/get-posts-ranked-query-options.ts

@feruzm feruzm added the patch Bug fixes and patches (1.0.0 → 1.0.1) label Aug 3, 2026
@feruzm
feruzm merged commit 1403b7d into develop Aug 3, 2026
3 of 4 checks passed
@feruzm
feruzm deleted the bugfix/sdk-ranked-posts-pagination branch August 3, 2026 15:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

patch Bug fixes and patches (1.0.0 → 1.0.1)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant