PR #13266: an empty page with more behind it stops paging and says so

PR #13266 · p11 /conversations, C2 and C6, part 1.

How it was captured. Two local dev servers on the stage data source: this branch and origin/main. Each is signed in as the smoke user and loads /conversations for 12 seconds. Every /api/conversations request is answered with the same fixture, no rows plus a cursor. That is the shape production returned 9 times in a row on 2026-09-28. No real rows are shown.

mainthis branch
List requests in 12 s2,322 (every one after the first carried a cursor)2 first-page loads (dev-mode double mount), 0 with a cursor
Empty title"No conversations found.""No conversations in what has loaded so far."
Way to read older rowsnone (it pages on its own, forever)"Load older conversations"
Count"0 of 4""0 of 4+", a lower bound
Details

Before (main)

main: No conversations found

The page claims the list is empty while the server still says more exists. Behind it, the table asks for the next page as soon as the last one lands: 2,322 requests in 12 seconds against a fixture that never ends. Production has an end, so there it was 9 serial requests and 8.8–16.8 s.

After (this branch)

branch: No conversations in what has loaded so far, Load older conversations banner

The page does not ask again after an empty page. The title says only what it knows, and the banner offers one manual step. The server half of this PR (reading past an all-filtered stretch within one request, with a named bound) is what makes an empty page with a cursor rare in the first place.

Written critique (two rounds)

  1. Round 1. On main, the empty state is a false claim with a runaway fetch behind it. The branch fixes both: the right words, and no automatic paging.
  2. Round 2. The title and the banner overlap a little. The banner adds the action and the note that counts and filters only cover loaded rows, so it stays. The banner sits under the table, next to the empty state it explains. The existing "Load older emails" pattern puts its banner above its table instead. Left as is and noted here.

Design check: no sparkle icons, no decorative chips, no emojis, and the existing Banner and empty-state components are reused.

PropFlow Docs