From e216c3bc97994b4172d15d52b46d5f6b75f35ea4 Mon Sep 17 00:00:00 2001
From: nsfisis <nsfisis@gmail.com>
Date: Fri, 13 Feb 2026 22:01:12 +0900
Subject: feat: add feed sidebar and cursor-based pagination

Add a feed sidebar to /unread and /read pages for filtering articles by
feed, and replace the fixed 100-article limit with cursor-based
pagination using a "Load more" button.

Backend:
- Add PageInfo, ArticleConnection types and pagination args to GraphQL
- Replace GetUnreadArticles/GetReadArticles with parameterized queries
- Add GetFeedUnreadCounts query and composite index
- Add shared pagination helper in resolver

Frontend:
- Add FeedSidebar component with unread count badges
- Add usePaginatedArticles hook for cursor-based fetching
- Update ArticleList with Load more button and single-feed mode
- Use ?feed=<id> query parameter for feed filtering

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---
 backend/db/queries/articles.sql | 32 ++++++++++++++++++++++++++------
 1 file changed, 26 insertions(+), 6 deletions(-)

(limited to 'backend/db/queries/articles.sql')

diff --git a/backend/db/queries/articles.sql b/backend/db/queries/articles.sql
index 2c00678..1554530 100644
--- a/backend/db/queries/articles.sql
+++ b/backend/db/queries/articles.sql
@@ -6,25 +6,45 @@ FROM articles AS a
 INNER JOIN feeds AS f ON a.feed_id = f.id
 WHERE a.id = ?;
 
--- name: GetUnreadArticles :many
+-- name: GetArticlesPaginated :many
 SELECT
     a.id, a.feed_id, a.guid, a.title, a.url, a.is_read,
     f.id as feed_id_2, f.url as feed_url, f.title as feed_title, f.is_subscribed as feed_is_subscribed
 FROM articles AS a
 INNER JOIN feeds AS f ON a.feed_id = f.id
-WHERE a.is_read = 0 AND f.is_subscribed = 1 AND f.user_id = ?
+WHERE a.is_read = ? AND f.is_subscribed = 1 AND f.user_id = ?
 ORDER BY a.id DESC
-LIMIT 100;
+LIMIT ?;
 
--- name: GetReadArticles :many
+-- name: GetArticlesPaginatedAfter :many
 SELECT
     a.id, a.feed_id, a.guid, a.title, a.url, a.is_read,
     f.id as feed_id_2, f.url as feed_url, f.title as feed_title, f.is_subscribed as feed_is_subscribed
 FROM articles AS a
 INNER JOIN feeds AS f ON a.feed_id = f.id
-WHERE a.is_read = 1 AND f.is_subscribed = 1 AND f.user_id = ?
+WHERE a.is_read = ? AND f.is_subscribed = 1 AND f.user_id = ? AND a.id < ?
 ORDER BY a.id DESC
-LIMIT 100;
+LIMIT ?;
+
+-- name: GetArticlesByFeedPaginated :many
+SELECT
+    a.id, a.feed_id, a.guid, a.title, a.url, a.is_read,
+    f.id as feed_id_2, f.url as feed_url, f.title as feed_title, f.is_subscribed as feed_is_subscribed
+FROM articles AS a
+INNER JOIN feeds AS f ON a.feed_id = f.id
+WHERE a.is_read = ? AND f.is_subscribed = 1 AND f.user_id = ? AND a.feed_id = ?
+ORDER BY a.id DESC
+LIMIT ?;
+
+-- name: GetArticlesByFeedPaginatedAfter :many
+SELECT
+    a.id, a.feed_id, a.guid, a.title, a.url, a.is_read,
+    f.id as feed_id_2, f.url as feed_url, f.title as feed_title, f.is_subscribed as feed_is_subscribed
+FROM articles AS a
+INNER JOIN feeds AS f ON a.feed_id = f.id
+WHERE a.is_read = ? AND f.is_subscribed = 1 AND f.user_id = ? AND a.feed_id = ? AND a.id < ?
+ORDER BY a.id DESC
+LIMIT ?;
 
 -- name: GetArticlesByFeed :many
 SELECT id, feed_id, guid, title, url, is_read
-- 
cgit v1.3-1-g0d28

