fix: Make endless list pagination deterministic

The filtered branch of `getByEndlessListId` applied `LIMIT` without an
`ORDER BY`, so Postgres was free to hand back any matching rows. With a
search over 64 cards the first page returned the oldest ones and a full
cursor walk reached 60 of them across 149 rows — cards both skipped and
repeated. The query now orders the way the cursor reads it.

The cursor itself is validated as ISO 8601, which admits forms Postgres
rejects as a timestamp (`2026`, `2026-W35-3`, a comma as the decimal
separator), each of them a 500 from the adapter. It is normalized before
the query, and stays a string because the equality half of the cursor
reads a `Date` as an empty constraint.
This commit is contained in:
Daniel Hiller
2026-08-26 09:23:01 +02:00
parent 4add7e43af
commit 2d289cd3d5
2 changed files with 13 additions and 1 deletions
@@ -108,6 +108,9 @@ const getByEndlessListId = async (listId, { before, search, userIds, labelIds })
query += ` AND card_label.label_id IN (${inValues.join(', ')})`;
}
// Must match the cursor built from the last returned card, otherwise the
// limit cuts an arbitrary slice and pages skip or repeat cards
query += ' ORDER BY card.list_changed_at DESC, card.id DESC';
query += ` LIMIT ${LIMIT}`;
let queryResult;