From 350caf6ace79d7d01133e101c039a32d5cf4884e Mon Sep 17 00:00:00 2001 From: arc53-machine <232052973+arc53-machine@users.noreply.github.com> Date: Tue, 22 Sep 2026 10:43:36 +0100 Subject: [PATCH] refactor(admin): make the activity keyset clause unconditional _page_after decided whether to append " AND " or " WHERE " by looking for "WHERE" in the SQL it had just been handed, which only worked because no union branch contains one. The query builder now always emits a WHERE, so adding a branch with its own predicate cannot silently break the cursor. --- docsgpt/storage/db/repositories/activity.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docsgpt/storage/db/repositories/activity.py b/docsgpt/storage/db/repositories/activity.py index 691f2c7e..2cfa8281 100644 --- a/docsgpt/storage/db/repositories/activity.py +++ b/docsgpt/storage/db/repositories/activity.py @@ -203,7 +203,9 @@ class ActivityRepository: "OR COALESCE(outcome, '') ILIKE :search OR detail::text ILIKE :search)" ) params["search"] = f"%{search}%" - where = ("WHERE " + " AND ".join(clauses)) if clauses else "" + # Always a WHERE, even with no filters, so the keyset cursor below can + # append " AND ..." without sniffing the string it was handed. + where = "WHERE " + " AND ".join(clauses or ["TRUE"]) return f"SELECT * FROM ({union}) AS activity {where}", params def list( @@ -269,8 +271,7 @@ class ActivityRepository: if not sql: return [] if cursor is not None: - sql += " AND " if "WHERE" in sql else " WHERE " - sql += "(created_at, feed, id) < (:cur_at, :cur_feed, :cur_id)" + sql += " AND (created_at, feed, id) < (:cur_at, :cur_feed, :cur_id)" params.update( {"cur_at": cursor[0], "cur_feed": cursor[1], "cur_id": cursor[2]} )