Repository: PotLock/curatedotfun
Pull request #88: Implements an "All" feed, and improves some misc UI
Author: login: elliotBraem
Created: 2025-03-13T05:42:51Z | Merged: 2025-03-13T06:05:22Z

- [x] "all" feed, new default route
- [x] able to see all pending for a given submission
- [x] filter approved, pending, rejected on all submssions
- [x] infinite scroll
- [x] mobile response cards
- [x] fix footer feedback link #29 
- [x] always show moderator, even without notes #56 

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added advanced submission filtering by status and refined pagination.
  - Introduced a download tool for exporting submission data.
  - Rolled out new interactive feed components with status badges and filter buttons.
  - Implemented a new feed index route for improved navigation.

- **Enhancements**
  - Implemented infinite scrolling for continuous content loading.
  - Streamlined navigation and feed routing for an improved user experience.
  - Updated responsive layouts and text styling for a cleaner interface.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/backend/src/routes/api/submission.ts b/backend/src/routes/api/submission.ts
index e91e03f8..30e9aefd 100644
--- a/backend/src/routes/api/submission.ts
+++ b/backend/src/routes/api/submission.ts
@@ -1,19 +1,65 @@
-import { zValidator } from "@hono/zod-validator";
 import { db } from "../../services/db";
-import { schemas } from "../../types/api";
 import { HonoApp } from "../../types/app";
+import { z } from "zod";
+import { zValidator } from "@hono/zod-validator";
+import { SubmissionStatus } from "../../types/twitter";
 
 // Create submission routes
 const router = HonoApp();
 
 /**
- * Get all submissions with optional pagination
+ * Get all submissions with optional status filtering and pagination
  */
-router.get("/", zValidator("query", schemas.pagination), async (c) => {
-  const { limit, offset } = c.req.valid("query");
+router.get(
+  "/",
+  zValidator(
+    "query",
+    z.object({
+      page: z.coerce.number().int().min(0).default(0),
+      limit: z.coerce.number().int().min(1).max(100).default(20),
+      status: z
+        .enum([
+          SubmissionStatus.PENDING,
+          SubmissionStatus.APPROVED,
+          SubmissionStatus.REJECTED,
+        ])
+        .optional(),
+    }),
+  ),
+  async (c) => {
+    // Extract validated parameters
+    const { page, limit, status } = c.req.valid("query");
 
-  return c.json(db.getAllSubmissions(limit, offset));
-});
+    // Get all submissions with the given status
+    const allSubmissions = db.getAllSubmissions(status);
+
+    // Sort submissions by submittedAt date (newest first)
+    allSubmissions.sort(
+      (a, b) =>
+        new Date(b.submittedAt!).getTime() - new Date(a.submittedAt!).getTime(),
+    );
+
+    // Get total count for pagination metadata
+    const totalCount = allSubmissions.length;
+
+    // Apply pagination
+    const startIndex = page * limit;
+    const endIndex = startIndex + limit;
+    const paginatedSubmissions = allSubmissions.slice(startIndex, endIndex);
+
+    // Return data with pagination metadata
+    return c.json({
+      items: paginatedSubmissions,
+      pagination: {
+        page,
+        limit,
+        totalCount,
+        totalPages: Math.ceil(totalCount / limit),
+        hasNextPage: endIndex < totalCount,
+      },
+    });
+  },
+);
 
 /**
  * Get a specific submission by ID
@@ -32,35 +78,23 @@ router.get("/single/:submissionId", async (c) => {
 /**
  * Get submissions for a specific feed
  */
-router.get(
-  "/feed/:feedId",
-  zValidator("query", schemas.pagination),
-  async (c) => {
-    const context = c.get("context");
-    const feedId = c.req.param("feedId");
-    const status = c.req.query("status");
-    const { limit, offset } = c.req.valid("query");
-
-    const feed = context.configService.getFeedConfig(feedId);
-    if (!feed) {
-      return c.notFound();
-    }
-
-    let submissions = await db.getSubmissionsByFeed(feedId);
-
-    if (status) {
-      submissions = submissions.filter((sub) => sub.status === status);
-    }
-
-    // Apply pagination if provided
-    if (limit || offset) {
-      const startIndex = offset || 0;
-      const endIndex = limit ? startIndex + limit : undefined;
-      submissions = submissions.slice(startIndex, endIndex);
-    }
-
-    return c.json(submissions);
-  },
-);
+router.get("/feed/:feedId", async (c) => {
+  const context = c.get("context");
+  const feedId = c.req.param("feedId");
+  const status = c.req.query("status");
+
+  const feed = context.configService.getFeedConfig(feedId);
+  if (!feed) {
+    return c.notFound();
+  }
+
+  let submissions = await db.getSubmissionsByFeed(feedId);
+
+  if (status) {
+    submissions = submissions.filter((sub) => sub.status === status);
+  }
+
+  return c.json(submissions);
+});
 
 export default router;
diff --git a/backend/src/services/db/index.ts b/backend/src/services/db/index.ts
index e2b5a54f..e6a62bfa 100644
--- a/backend/src/services/db/index.ts
+++ b/backend/src/services/db/index.ts
@@ -13,6 +13,7 @@ import {
   SubmissionStatus,
   TwitterCookie,
   TwitterSubmission,
+  TwitterSubmissionWithFeedData,
 } from "../../types/twitter";
 import * as twitterQueries from "../twitter/queries";
 export class DatabaseService {
@@ -79,8 +80,8 @@ export class DatabaseService {
     return queries.getSubmissionByCuratorTweetId(this.db, curatorTweetId);
   }
 
-  getAllSubmissions(limit?: number, offset?: number): TwitterSubmission[] {
-    return queries.getAllSubmissions(this.db, limit, offset);
+  getAllSubmissions(status?: string): TwitterSubmissionWithFeedData[] {
+    return queries.getAllSubmissions(this.db, status);
   }
 
   getDailySubmissionCount(userId: string): number {
diff --git a/backend/src/services/db/queries.ts b/backend/src/services/db/queries.ts
index a8091454..92481bf5 100644
--- a/backend/src/services/db/queries.ts
+++ b/backend/src/services/db/queries.ts
@@ -6,6 +6,7 @@ import {
   TwitterSubmission,
   SubmissionStatus,
   TwitterSubmissionWithFeedData,
+  FeedStatus,
 } from "../../types/twitter";
 import {
   feedPlugins,
@@ -350,54 +351,112 @@ export function getSubmission(
 
 export function getAllSubmissions(
   db: BetterSQLite3Database,
-  limit: number = 50,
-  offset: number = 0,
-): TwitterSubmission[] {
-  const results = db
-    .select({
-      s: {
-        tweetId: submissions.tweetId,
-        userId: submissions.userId,
-        username: submissions.username,
-        content: submissions.content,
-        curatorNotes: submissions.curatorNotes,
-        curatorId: submissions.curatorId,
-        curatorUsername: submissions.curatorUsername,
-        curatorTweetId: submissions.curatorTweetId,
-        createdAt: submissions.createdAt,
-        submittedAt: sql<string>`COALESCE(${submissions.submittedAt}, ${submissions.createdAt})`,
-      },
-      m: {
-        tweetId: moderationHistory.tweetId,
-        adminId: moderationHistory.adminId,
-        action: moderationHistory.action,
-        note: moderationHistory.note,
-        createdAt: moderationHistory.createdAt,
-        feedId: moderationHistory.feedId,
-        moderationResponseTweetId: submissionFeeds.moderationResponseTweetId,
-      },
-    })
-    .from(submissions)
-    .leftJoin(
-      moderationHistory,
-      eq(submissions.tweetId, moderationHistory.tweetId),
-    )
-    .leftJoin(
-      submissionFeeds,
-      and(
-        eq(submissions.tweetId, submissionFeeds.submissionId),
-        eq(moderationHistory.feedId, submissionFeeds.feedId),
-      ),
-    )
-    .orderBy(moderationHistory.createdAt)
-    .limit(limit)
-    .offset(offset)
-    .all() as DbQueryResult[];
+  status?: string,
+): TwitterSubmissionWithFeedData[] {
+  // Build the query with or without status filter
+  const queryBuilder = status
+    ? db
+        .select({
+          s: {
+            tweetId: submissions.tweetId,
+            userId: submissions.userId,
+            username: submissions.username,
+            content: submissions.content,
+            curatorNotes: submissions.curatorNotes,
+            curatorId: submissions.curatorId,
+            curatorUsername: submissions.curatorUsername,
+            curatorTweetId: submissions.curatorTweetId,
+            createdAt: submissions.createdAt,
+            submittedAt: sql<string>`COALESCE(${submissions.submittedAt}, ${submissions.createdAt})`,
+          },
+          m: {
+            tweetId: moderationHistory.tweetId,
+            adminId: moderationHistory.adminId,
+            action: moderationHistory.action,
+            note: moderationHistory.note,
+            createdAt: moderationHistory.createdAt,
+            feedId: moderationHistory.feedId,
+            moderationResponseTweetId:
+              submissionFeeds.moderationResponseTweetId,
+          },
+          sf: {
+            submissionId: submissionFeeds.submissionId,
+            feedId: submissionFeeds.feedId,
+            status: submissionFeeds.status,
+            moderationResponseTweetId:
+              submissionFeeds.moderationResponseTweetId,
+          },
+          f: {
+            id: feeds.id,
+            name: feeds.name,
+          },
+        })
+        .from(submissions)
+        .leftJoin(
+          moderationHistory,
+          eq(submissions.tweetId, moderationHistory.tweetId),
+        )
+        .leftJoin(
+          submissionFeeds,
+          eq(submissions.tweetId, submissionFeeds.submissionId),
+        )
+        .leftJoin(feeds, eq(submissionFeeds.feedId, feeds.id))
+        .where(eq(submissionFeeds.status, status as SubmissionStatus))
+    : db
+        .select({
+          s: {
+            tweetId: submissions.tweetId,
+            userId: submissions.userId,
+            username: submissions.username,
+            content: submissions.content,
+            curatorNotes: submissions.curatorNotes,
+            curatorId: submissions.curatorId,
+            curatorUsername: submissions.curatorUsername,
+            curatorTweetId: submissions.curatorTweetId,
+            createdAt: submissions.createdAt,
+            submittedAt: sql<string>`COALESCE(${submissions.submittedAt}, ${submissions.createdAt})`,
+          },
+          m: {
+            tweetId: moderationHistory.tweetId,
+            adminId: moderationHistory.adminId,
+            action: moderationHistory.action,
+            note: moderationHistory.note,
+            createdAt: moderationHistory.createdAt,
+            feedId: moderationHistory.feedId,
+            moderationResponseTweetId:
+              submissionFeeds.moderationResponseTweetId,
+          },
+          sf: {
+            submissionId: submissionFeeds.submissionId,
+            feedId: submissionFeeds.feedId,
+            status: submissionFeeds.status,
+            moderationResponseTweetId:
+              submissionFeeds.moderationResponseTweetId,
+          },
+          f: {
+            id: feeds.id,
+            name: feeds.name,
+          },
+        })
+        .from(submissions)
+        .leftJoin(
+          moderationHistory,
+          eq(submissions.tweetId, moderationHistory.tweetId),
+        )
+        .leftJoin(
+          submissionFeeds,
+          eq(submissions.tweetId, submissionFeeds.submissionId),
+        )
+        .leftJoin(feeds, eq(submissionFeeds.feedId, feeds.id));
+
+  const results = queryBuilder.orderBy(moderationHistory.createdAt).all();
 
   // Group results by submission
-  const submissionMap = new Map<string, TwitterSubmission>();
+  const submissionMap = new Map<string, TwitterSubmissionWithFeedData>();
+  const feedStatusMap = new Map<string, Map<string, FeedStatus>>();
 
   for (const result of results) {
+    // Initialize submission if not exists
     if (!submissionMap.has(result.s.tweetId)) {
       submissionMap.set(result.s.tweetId, {
         tweetId: result.s.tweetId,
@@ -411,9 +470,17 @@ export function getAllSubmissions(
         createdAt: result.s.createdAt,
         submittedAt: result.s.submittedAt,
         moderationHistory: [],
+        status: status
+          ? (status as SubmissionStatus)
+          : SubmissionStatus.PENDING, // Use provided status or default
+        feedStatuses: [],
       });
+
+      // Initialize feed status map for this submission
+      feedStatusMap.set(result.s.tweetId, new Map<string, FeedStatus>());
     }
 
+    // Add moderation history
     if (result.m && result.m.adminId !== null) {
       const submission = submissionMap.get(result.s.tweetId)!;
       submission.moderationHistory.push({
@@ -427,6 +494,75 @@ export function getAllSubmissions(
           result.m.moderationResponseTweetId ?? undefined,
       });
     }
+
+    // Add feed status if available
+    if (result.sf?.feedId && result.f?.id) {
+      // If status is provided, only include feeds with that status
+      if (!status || result.sf.status === status) {
+        const feedStatusesForSubmission = feedStatusMap.get(result.s.tweetId)!;
+
+        if (!feedStatusesForSubmission.has(result.sf.feedId)) {
+          feedStatusesForSubmission.set(result.sf.feedId, {
+            feedId: result.sf.feedId,
+            feedName: result.f.name,
+            status: result.sf.status,
+            moderationResponseTweetId:
+              result.sf.moderationResponseTweetId ?? undefined,
+          });
+        }
+      }
+    }
+  }
+
+  // Set the feed statuses and determine the main status for each submission
+  for (const [tweetId, submission] of submissionMap.entries()) {
+    const feedStatusesForSubmission = feedStatusMap.get(tweetId);
+    if (feedStatusesForSubmission) {
+      submission.feedStatuses = Array.from(feedStatusesForSubmission.values());
+
+      // Determine the main status based on priority (pending > rejected > approved)
+      let hasPending = false;
+      let hasRejected = false;
+      let hasApproved = false;
+
+      for (const feedStatus of submission.feedStatuses) {
+        if (feedStatus.status === SubmissionStatus.PENDING) {
+          hasPending = true;
+          submission.status = SubmissionStatus.PENDING;
+          submission.moderationResponseTweetId =
+            feedStatus.moderationResponseTweetId;
+          break; // Pending has highest priority
+        } else if (feedStatus.status === SubmissionStatus.REJECTED) {
+          hasRejected = true;
+        } else if (feedStatus.status === SubmissionStatus.APPROVED) {
+          hasApproved = true;
+        }
+      }
+
+      if (!hasPending) {
+        if (hasRejected) {
+          submission.status = SubmissionStatus.REJECTED;
+          // Find first rejected status for moderationResponseTweetId
+          const rejectedStatus = submission.feedStatuses.find(
+            (fs) => fs.status === SubmissionStatus.REJECTED,
+          );
+          if (rejectedStatus) {
+            submission.moderationResponseTweetId =
+              rejectedStatus.moderationResponseTweetId;
+          }
+        } else if (hasApproved) {
+          submission.status = SubmissionStatus.APPROVED;
+          // Find first approved status for moderationResponseTweetId
+          const approvedStatus = submission.feedStatuses.find(
+            (fs) => fs.status === SubmissionStatus.APPROVED,
+          );
+          if (approvedStatus) {
+            submission.moderationResponseTweetId =
+              approvedStatus.moderationResponseTweetId;
+          }
+        }
+      }
+    }
   }
 
   return Array.from(submissionMap.values());
diff --git a/backend/src/types/twitter.ts b/backend/src/types/twitter.ts
index 38a73b5b..7d110a5a 100644
--- a/backend/src/types/twitter.ts
+++ b/backend/src/types/twitter.ts
@@ -1,3 +1,10 @@
+export interface FeedStatus {
+  feedId: string;
+  feedName: string;
+  status: SubmissionStatus;
+  moderationResponseTweetId?: string;
+}
+
 export interface TwitterSubmission {
   tweetId: string;
   userId: string;
@@ -16,6 +23,7 @@ export interface TwitterSubmission {
 export interface TwitterSubmissionWithFeedData extends TwitterSubmission {
   status: SubmissionStatus;
   moderationResponseTweetId?: string;
+  feedStatuses?: FeedStatus[];
 }
 
 export interface Moderation {
diff --git a/frontend/bun.lockb b/frontend/bun.lockb
index 4ebe11a7..9f860594 100755
Binary files a/frontend/bun.lockb and b/frontend/bun.lockb differ
diff --git a/frontend/package.json b/frontend/package.json
index ad2da768..765fd9b2 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -12,8 +12,7 @@
   "dependencies": {
     "@tailwindcss/typography": "^0.5.15",
     "@tanstack/react-query": "^5.64.1",
-    "@tanstack/react-router": "^1.97.0",
-    "@tanstack/router-devtools": "^1.97.0",
+    "@tanstack/react-router": "^1.114.15",
     "autoprefixer": "^10.4.20",
     "axios": "^1.7.9",
     "postcss": "^8.4.49",
@@ -28,7 +27,8 @@
     "@mswjs/data": "^0.16.2",
     "@rsbuild/core": "1.1.13",
     "@rsbuild/plugin-react": "1.1.0",
-    "@tanstack/router-plugin": "^1.97.0",
+    "@tanstack/router-devtools": "^1.114.15",
+    "@tanstack/router-plugin": "^1.114.15",
     "@types/bun": "^1.1.16",
     "@types/react": "^18.3.12",
     "@types/react-dom": "^18.3.1",
diff --git a/frontend/src/components/DownloadButton.tsx b/frontend/src/components/DownloadButton.tsx
new file mode 100644
index 00000000..b37515f0
--- /dev/null
+++ b/frontend/src/components/DownloadButton.tsx
@@ -0,0 +1,89 @@
+import { useState } from "react";
+import {
+  TwitterSubmission,
+  TwitterSubmissionWithFeedData,
+} from "../types/twitter";
+import { Modal } from "./Modal";
+
+interface DownloadButtonProps {
+  items: TwitterSubmissionWithFeedData[];
+  feedName?: string | undefined;
+}
+
+const DownloadButton = ({ items, feedName = "all" }: DownloadButtonProps) => {
+  const [isDownloadModalOpen, setIsDownloadModalOpen] = useState(false);
+
+  const handleDownload = (
+    selectedStatus: "all" | TwitterSubmission["status"],
+  ) => {
+    const itemsToDownload = items.filter(
+      (item) => selectedStatus === "all" || item.status === selectedStatus,
+    );
+    const jsonContent = JSON.stringify(itemsToDownload, null, 2);
+    const blob = new Blob([jsonContent], { type: "application/json" });
+    const url = URL.createObjectURL(blob);
+    const a = document.createElement("a");
+    try {
+      a.href = url;
+      a.download = `${feedName}_${selectedStatus}_submissions.json`;
+      document.body.appendChild(a);
+      a.click();
+      document.body.removeChild(a);
+    } catch (error) {
+      console.error("Download failed:", error);
+    } finally {
+      URL.revokeObjectURL(url);
+      setIsDownloadModalOpen(false);
+    }
+  };
+
+  const DownloadModal = () => (
+    <Modal
+      isOpen={isDownloadModalOpen}
+      onClose={() => setIsDownloadModalOpen(false)}
+    >
+      <h2 className="text-2xl font-bold mb-4">Download Submissions</h2>
+      <p className="mb-4">Select which submissions you want to download:</p>
+      <div className="space-y-2">
+        <button
+          onClick={() => handleDownload("all")}
+          className="w-full px-4 py-2 bg-gray-100 hover:bg-gray-200 text-black rounded-md border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-all duration-200 text-sm font-medium"
+        >
+          All Submissions
+        </button>
+        <button
+          onClick={() => handleDownload("approved")}
+          className="w-full px-4 py-2 bg-green-200 hover:bg-green-300 text-black rounded-md border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-all duration-200 text-sm font-medium"
+        >
+          Approved Submissions
+        </button>
+        <button
+          onClick={() => handleDownload("pending")}
+          className="w-full px-4 py-2 bg-yellow-200 hover:bg-yellow-300 text-black rounded-md border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-all duration-200 text-sm font-medium"
+        >
+          Pending Submissions
+        </button>
+        <button
+          onClick={() => handleDownload("rejected")}
+          className="w-full px-4 py-2 bg-red-200 hover:bg-red-300 text-black rounded-md border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-all duration-200 text-sm font-medium"
+        >
+          Rejected Submissions
+        </button>
+      </div>
+    </Modal>
+  );
+
+  return (
+    <>
+      <DownloadModal />
+      <button
+        onClick={() => setIsDownloadModalOpen(true)}
+        className="px-3 py-1.5 bg-blue-500 hover:bg-blue-600 text-white rounded-md border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-all duration-200 translate-x-0 translate-y-0 hover:-translate-x-0.5 hover:-translate-y-0.5 text-sm font-medium"
+      >
+        Download
+      </button>
+    </>
+  );
+};
+
+export default DownloadButton;
diff --git a/frontend/src/components/FeedHeader.tsx b/frontend/src/components/FeedHeader.tsx
new file mode 100644
index 00000000..98093dbc
--- /dev/null
+++ b/frontend/src/components/FeedHeader.tsx
@@ -0,0 +1,46 @@
+import StatusFilterButtons from "./StatusFilterButtons";
+import DownloadButton from "./DownloadButton";
+import {
+  TwitterSubmission,
+  TwitterSubmissionWithFeedData,
+} from "../types/twitter";
+
+interface FeedHeaderProps {
+  title: string;
+  description: string;
+  items: TwitterSubmissionWithFeedData[];
+  statusFilter: "all" | TwitterSubmission["status"];
+  setStatusFilter: (status: "all" | TwitterSubmission["status"]) => void;
+  feedName?: string | undefined;
+}
+
+const FeedHeader = ({
+  title,
+  description,
+  items,
+  statusFilter,
+  setStatusFilter,
+  feedName,
+}: FeedHeaderProps) => {
+  return (
+    <div className="grid grid-cols-1 xl:grid-cols-2 gap-4 mb-6">
+      <div>
+        <h2 className="text-2xl font-bold mb-2">{title}</h2>
+        <p className="text-sm sm:text-base text-gray-600">{description}</p>
+      </div>
+      <div className="flex flex-wrap gap-2 items-start xl:items-center xl:justify-end">
+        <div className="w-full sm:w-auto flex flex-wrap gap-2">
+          <StatusFilterButtons
+            statusFilter={statusFilter}
+            setStatusFilter={setStatusFilter}
+          />
+        </div>
+        <div className="w-full sm:w-auto mt-2 sm:mt-0">
+          <DownloadButton items={items} feedName={feedName} />
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default FeedHeader;
diff --git a/frontend/src/components/FeedItem.tsx b/frontend/src/components/FeedItem.tsx
index 6fd056f1..5abbe39c 100644
--- a/frontend/src/components/FeedItem.tsx
+++ b/frontend/src/components/FeedItem.tsx
@@ -1,7 +1,13 @@
-import { HiExternalLink } from "react-icons/hi";
-import { TwitterSubmissionWithFeedData } from "../types/twitter";
+import { HiExternalLink, HiChevronDown, HiChevronUp } from "react-icons/hi";
+import {
+  FeedStatus,
+  SubmissionStatus,
+  TwitterSubmissionWithFeedData,
+} from "../types/twitter";
 import { getTweetUrl, handleApprove, handleReject } from "../lib/twitter";
 import { useBotId } from "../lib/config";
+import { useState, useRef, useEffect } from "react";
+import { Link } from "@tanstack/react-router";
 const formatDate = (dateString: string) => {
   return new Date(dateString).toLocaleString();
 };
@@ -40,9 +46,13 @@ const TweetLink = ({
 
 const StatusBadge = ({
   status,
+  feedId,
+  feedName,
   clickable = false,
 }: {
   status: TwitterSubmissionWithFeedData["status"];
+  feedId?: string;
+  feedName?: string;
   clickable?: boolean;
 }) => {
   const baseClasses = "status-badge px-2 py-1 rounded-md text-sm font-medium";
@@ -52,9 +62,153 @@ const StatusBadge = ({
     rejected: "bg-red-200 text-black",
   };
   const classes = `${baseClasses} ${statusClasses[status]} ${clickable ? "cursor-pointer hover:opacity-80" : ""}`;
+
+  if (feedId) {
+    return (
+      <Link
+        to="/feed/$feedId"
+        params={{ feedId }}
+        className={classes}
+        title={feedName}
+      >
+        {status}
+      </Link>
+    );
+  }
+
   return <span className={classes}>{status}</span>;
 };
 
+// Feed Status Badges component
+const FeedStatusBadges = ({
+  feedStatuses,
+  statusFilter,
+}: {
+  feedStatuses?: FeedStatus[];
+  statusFilter: "all" | SubmissionStatus;
+}) => {
+  const [isExpanded, setIsExpanded] = useState(false);
+  const popoverRef = useRef<HTMLDivElement>(null);
+
+  // Close popover when clicking outside
+  useEffect(() => {
+    const handleClickOutside = (event: MouseEvent) => {
+      if (
+        popoverRef.current &&
+        !popoverRef.current.contains(event.target as Node)
+      ) {
+        setIsExpanded(false);
+      }
+    };
+
+    document.addEventListener("mousedown", handleClickOutside);
+    return () => {
+      document.removeEventListener("mousedown", handleClickOutside);
+    };
+  }, []);
+
+  if (!feedStatuses || feedStatuses.length === 0) {
+    return null;
+  }
+
+  // Filter statuses based on statusFilter
+  const filteredStatuses =
+    statusFilter === "all"
+      ? feedStatuses
+      : feedStatuses.filter((fs) => fs.status === statusFilter);
+
+  if (filteredStatuses.length === 0) {
+    return null;
+  }
+
+  // Show only 1 badge on mobile/tablet, up to 3 on desktop
+  const maxVisibleBadges = {
+    mobile: 1,
+    desktop: 3,
+  };
+
+  return (
+    <div className="relative">
+      <div className="flex flex-wrap gap-1">
+        {/* Mobile view - show only 1 badge */}
+        <div className="md:hidden">
+          {filteredStatuses.slice(0, maxVisibleBadges.mobile).map((fs) => (
+            <StatusBadge
+              key={fs.feedId}
+              status={fs.status}
+              feedId={fs.feedId}
+              feedName={fs.feedName}
+              clickable
+            />
+          ))}
+
+          {filteredStatuses.length > maxVisibleBadges.mobile && (
+            <button
+              onClick={() => setIsExpanded(!isExpanded)}
+              className="px-2 py-1 rounded-md text-sm font-medium bg-gray-200 text-black flex items-center"
+            >
+              +{filteredStatuses.length - maxVisibleBadges.mobile} more
+              {isExpanded ? (
+                <HiChevronUp className="ml-1" />
+              ) : (
+                <HiChevronDown className="ml-1" />
+              )}
+            </button>
+          )}
+        </div>
+
+        {/* Desktop view - show up to 3 badges */}
+        <div className="hidden md:flex md:flex-wrap md:gap-1">
+          {filteredStatuses.slice(0, maxVisibleBadges.desktop).map((fs) => (
+            <StatusBadge
+              key={fs.feedId}
+              status={fs.status}
+              feedId={fs.feedId}
+              feedName={fs.feedName}
+              clickable
+            />
+          ))}
+
+          {filteredStatuses.length > maxVisibleBadges.desktop && (
+            <button
+              onClick={() => setIsExpanded(!isExpanded)}
+              className="px-2 py-1 rounded-md text-sm font-medium bg-gray-200 text-black flex items-center"
+            >
+              +{filteredStatuses.length - maxVisibleBadges.desktop} more
+              {isExpanded ? (
+                <HiChevronUp className="ml-1" />
+              ) : (
+                <HiChevronDown className="ml-1" />
+              )}
+            </button>
+          )}
+        </div>
+      </div>
+
+      {/* Popover for expanded view */}
+      {isExpanded && (
+        <div
+          ref={popoverRef}
+          className="absolute top-full left-0 mt-1 p-2 bg-white border-2 border-black shadow-sharp rounded-md z-10 min-w-[200px]"
+        >
+          <h4 className="font-medium mb-2">All Feeds</h4>
+          <div className="space-y-2">
+            {filteredStatuses.map((fs) => (
+              <div
+                key={fs.feedId}
+                className="flex justify-between items-center"
+              >
+                <span className="text-sm">{fs.feedName}</span>
+                <StatusBadge status={fs.status} feedId={fs.feedId} clickable />
+              </div>
+            ))}
+          </div>
+        </div>
+      )}
+    </div>
+  );
+};
+
 const NotesSection = ({
   title,
   username,
@@ -67,26 +221,35 @@ const NotesSection = ({
   tweetId: string;
   note: string | null;
   className?: string;
-}) => (
-  <div
-    className={`p-4 border-2 border-gray-200 rounded-md bg-gray-50 ${className}`}
-  >
-    <div className="flex items-center gap-2 mb-2">
-      <h4 className="heading-3">{title}</h4>
-      <span className="text-gray-400">·</span>
-      <div className="text-gray-600">
-        by <UserLink username={username} />
-        <span className="text-gray-400 mx-1">·</span>
-        <TweetLink
-          tweetId={tweetId}
-          username={username}
-          title={`View ${title.toLowerCase()} on X/Twitter`}
-        />
+}) => {
+  // Change title based on whether there are notes or not
+  const displayTitle = note
+    ? title
+    : title === "Moderation Notes"
+      ? "Moderated"
+      : "Curated";
+
+  return (
+    <div
+      className={`p-4 border-2 border-gray-200 rounded-md bg-gray-50 ${className}`}
+    >
+      <div className="flex flex-wrap items-center gap-2 mb-2">
+        <h4 className="heading-3">{displayTitle}</h4>
+        <span className="text-gray-400">·</span>
+        <div className="text-gray-600 break-words">
+          by <UserLink username={username} />
+          <span className="text-gray-400 mx-1">·</span>
+          <TweetLink
+            tweetId={tweetId}
+            username={username}
+            title={`View ${title.toLowerCase()} on X/Twitter`}
+          />
+        </div>
       </div>
+      {note && <p className="body-text text-gray-700">{note}</p>}
     </div>
-    {note && <p className="body-text text-gray-700">{note}</p>}
-  </div>
-);
+  );
+};
 
 const ModerationActions = ({
   submission,
@@ -115,14 +278,18 @@ const ModerationActions = ({
 
 interface FeedItemProps {
   submission: TwitterSubmissionWithFeedData;
+  statusFilter: "all" | SubmissionStatus;
 }
 
-export const FeedItem = ({ submission }: FeedItemProps) => {
+export const FeedItem = ({
+  submission,
+  statusFilter = "all",
+}: FeedItemProps) => {
   const lastModeration =
     submission.moderationHistory?.[submission.moderationHistory.length - 1];
 
   return (
-    <div className="card">
+    <div className="card" id={submission.tweetId}>
       {/* Header Section */}
       <div className="flex justify-between items-start">
         <div className="flex-grow">
@@ -144,22 +311,33 @@ export const FeedItem = ({ submission }: FeedItemProps) => {
             </span>
           </div>
         </div>
-        <a
-          href={getTweetUrl(
-            submission.curatorTweetId,
-            submission.curatorUsername,
-          )}
-          target="_blank"
-          rel="noopener noreferrer"
-        >
-          <StatusBadge status={submission.status} clickable />
-        </a>
+
+        {/* Show feed statuses if available, otherwise show the main status */}
+        {submission.feedStatuses && submission.feedStatuses.length > 0 ? (
+          <FeedStatusBadges
+            feedStatuses={submission.feedStatuses}
+            statusFilter={statusFilter as "all" | SubmissionStatus}
+          />
+        ) : (
+          <a
+            href={getTweetUrl(
+              submission.curatorTweetId,
+              submission.curatorUsername,
+            )}
+            target="_blank"
+            rel="noopener noreferrer"
+          >
+            <StatusBadge status={submission.status} clickable />
+          </a>
+        )}
       </div>
 
       {/* Content Section */}
-      <p className="text-lg leading-relaxed body-text pt-2">
-        {submission.content}
-      </p>
+      <div className="w-full overflow-hidden">
+        <p className="text-lg leading-relaxed body-text pt-2 break-words overflow-wrap-anywhere">
+          {submission.content}
+        </p>
+      </div>
 
       {/* Notes Section */}
       <div className="mt-6">
@@ -167,27 +345,29 @@ export const FeedItem = ({ submission }: FeedItemProps) => {
         {(submission.status === "approved" ||
           submission.status === "rejected") &&
           lastModeration && (
-            <NotesSection
-              title="Moderation Notes"
-              username={lastModeration.adminId}
-              tweetId={submission.moderationResponseTweetId!}
-              note={lastModeration.note}
-              className="mb-4"
-            />
+            <div className="flex">
+              <div className="flex-col flex-grow">
+                <NotesSection
+                  title="Moderation Notes"
+                  username={lastModeration.adminId}
+                  tweetId={submission.moderationResponseTweetId!}
+                  note={lastModeration.note}
+                  className="mb-4"
+                />
+              </div>
+            </div>
           )}
 
         {/* Curator Notes and Moderation Actions */}
         {submission.status === "pending" && (
           <div className="flex gap-8">
             <div className="flex-col flex-grow">
-              {submission.curatorNotes?.trim() && (
-                <NotesSection
-                  title="Curator's Notes"
-                  username={submission.curatorUsername}
-                  tweetId={submission.curatorTweetId}
-                  note={submission.curatorNotes}
-                />
-              )}
+              <NotesSection
+                title="Curator's Notes"
+                username={submission.curatorUsername}
+                tweetId={submission.curatorTweetId}
+                note={submission.curatorNotes}
+              />
             </div>
             <div className="flex-col">
               <div className="flex">
diff --git a/frontend/src/components/FeedList.tsx b/frontend/src/components/FeedList.tsx
index bb323c4c..dbaff658 100644
--- a/frontend/src/components/FeedList.tsx
+++ b/frontend/src/components/FeedList.tsx
@@ -1,11 +1,9 @@
 import { useQuery } from "@tanstack/react-query";
-import { Link, useParams } from "@tanstack/react-router";
+import { Link } from "@tanstack/react-router";
 import { FaChevronRight } from "react-icons/fa";
 import type { FeedConfig } from "../types/config";
 
-const FeedList = () => {
-  const { feedId } = useParams({ from: "/feed/$feedId" });
-
+const FeedList = ({ selectedFeedId }: { selectedFeedId?: string }) => {
   const { data: feeds = [] } = useQuery<FeedConfig[]>({
     queryKey: ["feeds"],
     queryFn: async () => {
@@ -40,7 +38,7 @@ const FeedList = () => {
               to="/feed/$feedId"
               params={{ feedId: feed.id }}
               className={`flex-shrink-0 min-w-[200px] mx-2 md:mx-0 md:min-w-0 block px-4 py-2 text-sm border-2 border-black shadow-sharp transition-all duration-200 md:mb-2 ${
-                feedId === feed.id
+                selectedFeedId === feed.id
                   ? "bg-gray-100 text-black font-medium translate-x-0.5 translate-y-0.5 shadow-none"
                   : "text-gray-600 hover:shadow-sharp-hover hover:-translate-x-0.5 hover:-translate-y-0.5 hover:bg-gray-50"
               }`}
diff --git a/frontend/src/components/InfiniteFeed.tsx b/frontend/src/components/InfiniteFeed.tsx
new file mode 100644
index 00000000..ef3bdd13
--- /dev/null
+++ b/frontend/src/components/InfiniteFeed.tsx
@@ -0,0 +1,76 @@
+import { useRef, useEffect, useCallback, ReactNode } from "react";
+
+interface InfiniteFeedProps<T> {
+  items: T[];
+  renderItems: (items: T[]) => ReactNode;
+  fetchNextPage: () => void;
+  hasNextPage?: boolean;
+  isFetchingNextPage: boolean;
+  status: "pending" | "error" | "success";
+  loadingMessage?: string;
+  noMoreItemsMessage?: string;
+  initialLoadingMessage?: string;
+}
+
+function InfiniteFeed<T>({
+  items,
+  renderItems,
+  fetchNextPage,
+  hasNextPage = false,
+  isFetchingNextPage,
+  status,
+  loadingMessage = "Loading more items...",
+  noMoreItemsMessage = "No more items to load",
+  initialLoadingMessage = "Loading items...",
+}: InfiniteFeedProps<T>) {
+  // Create an intersection observer to detect when user scrolls to bottom
+  const observerTarget = useRef<HTMLDivElement>(null);
+
+  const handleObserver = useCallback(
+    (entries: IntersectionObserverEntry[]) => {
+      const [entry] = entries;
+      if (entry.isIntersecting && hasNextPage && !isFetchingNextPage) {
+        fetchNextPage();
+      }
+    },
+    [fetchNextPage, hasNextPage, isFetchingNextPage],
+  );
+
+  useEffect(() => {
+    const element = observerTarget.current;
+    if (!element) return;
+
+    const observer = new IntersectionObserver(handleObserver, {
+      root: null,
+      rootMargin: "100px", // Start loading a bit earlier
+      threshold: 0.1, // Trigger when just 10% of the element is visible
+    });
+
+    observer.observe(element);
+    return () => {
+      observer.unobserve(element);
+      observer.disconnect();
+    };
+  }, [handleObserver]);
+
+  return (
+    <div className="space-y-4">
+      {renderItems(items)}
+
+      {/* Loading indicator and observer target */}
+      <div ref={observerTarget} className="py-4 flex justify-center">
+        {isFetchingNextPage && (
+          <div className="text-gray-500">{loadingMessage}</div>
+        )}
+        {!hasNextPage && items.length > 0 && !isFetchingNextPage && (
+          <div className="text-gray-500">{noMoreItemsMessage}</div>
+        )}
+        {status === "pending" && items.length === 0 && (
+          <div className="text-gray-500">{initialLoadingMessage}</div>
+        )}
+      </div>
+    </div>
+  );
+}
+
+export default InfiniteFeed;
diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx
index f5e33487..f8f49053 100644
--- a/frontend/src/components/Layout.tsx
+++ b/frontend/src/components/Layout.tsx
@@ -69,24 +69,26 @@ const Layout = ({ children, sidebar, rightPanel }: LayoutProps) => {
           )}
         </div>
       </div>
-      <footer className="fixed bottom-0 w-full py-2 sm:py-4 text-center bg-white/80 backdrop-blur text-sm sm:text-base border-t-2 border-black">
+      <footer className="fixed bottom-0 w-full py-2 sm:py-4 bg-white/80 backdrop-blur text-sm sm:text-base border-t-2 border-black">
         <div className="relative max-w-7xl mx-auto px-4">
-          <a
-            href="https://potlock.org"
-            className="hover:text-gray-800"
-            target="_blank"
-            rel="noopener noreferrer"
-          >
-            Curated with ❤️ by 🫕 POTLOCK
-          </a>
-          <a
-            href="https://github.com/PotLock/curatedotfun/issues"
-            className="absolute right-4 top-1/2 -translate-y-1/2 px-4 py-1 border-2 border-black hover:bg-black hover:text-white transition-colors rounded-md"
-            target="_blank"
-            rel="noopener noreferrer"
-          >
-            Feedback
-          </a>
+          <div className="flex justify-between items-center sm:block sm:text-center">
+            <a
+              href="https://potlock.org"
+              className="hover:text-gray-800"
+              target="_blank"
+              rel="noopener noreferrer"
+            >
+              {"Curated with ❤️ by 🫕 POTLOCK"}
+            </a>
+            <a
+              href="https://github.com/PotLock/curatedotfun/issues"
+              className="sm:absolute sm:right-4 sm:top-1/2 sm:-translate-y-1/2 px-4 py-1 border-2 border-black hover:bg-black hover:text-white transition-colors rounded-md"
+              target="_blank"
+              rel="noopener noreferrer"
+            >
+              Feedback
+            </a>
+          </div>
         </div>
       </footer>
     </div>
diff --git a/frontend/src/components/StatusFilterButtons.tsx b/frontend/src/components/StatusFilterButtons.tsx
new file mode 100644
index 00000000..284c24e5
--- /dev/null
+++ b/frontend/src/components/StatusFilterButtons.tsx
@@ -0,0 +1,48 @@
+import { SubmissionStatus, TwitterSubmission } from "../types/twitter";
+
+interface StatusFilterButtonsProps {
+  statusFilter: "all" | TwitterSubmission["status"];
+  setStatusFilter: (status: "all" | TwitterSubmission["status"]) => void;
+}
+
+const StatusFilterButtons = ({
+  statusFilter,
+  setStatusFilter,
+}: StatusFilterButtonsProps) => {
+  return (
+    <div className="flex flex-wrap gap-2 items-start xl:items-center xl:justify-end">
+      {[
+        { value: "all", label: "All", activeClass: "bg-black text-white" },
+        {
+          value: "pending",
+          label: "Pending",
+          activeClass: "bg-yellow-200 text-black",
+        },
+        {
+          value: "approved",
+          label: "Approved",
+          activeClass: "bg-green-200 text-black",
+        },
+        {
+          value: "rejected",
+          label: "Rejected",
+          activeClass: "bg-red-200 text-black",
+        },
+      ].map(({ value, label, activeClass }) => (
+        <button
+          key={value}
+          onClick={() => setStatusFilter(value as SubmissionStatus)}
+          className={`px-3 py-1.5 rounded-md border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-all duration-200 translate-x-0 translate-y-0 hover:-translate-x-0.5 hover:-translate-y-0.5 text-sm font-medium ${
+            statusFilter === value
+              ? activeClass
+              : "bg-gray-100 hover:bg-gray-200 text-black"
+          }`}
+        >
+          {label}
+        </button>
+      ))}
+    </div>
+  );
+};
+
+export default StatusFilterButtons;
diff --git a/frontend/src/components/SubmissionList.tsx b/frontend/src/components/SubmissionList.tsx
new file mode 100644
index 00000000..8753a5ec
--- /dev/null
+++ b/frontend/src/components/SubmissionList.tsx
@@ -0,0 +1,67 @@
+import {
+  TwitterSubmission,
+  TwitterSubmissionWithFeedData,
+} from "../types/twitter";
+import FeedItem from "./FeedItem";
+
+interface SubmissionListProps {
+  items: TwitterSubmissionWithFeedData[];
+  statusFilter: "all" | TwitterSubmission["status"];
+  botId: string | undefined;
+  feedId?: string | undefined;
+}
+
+const SubmissionList = ({
+  items,
+  statusFilter,
+  botId,
+  feedId,
+}: SubmissionListProps) => {
+  // Filter items based on feed statuses if available
+  const filteredItems = items.filter((item) => {
+    // If no feed statuses, use the main status
+    if (!item.feedStatuses || item.feedStatuses.length === 0) {
+      return statusFilter === "all" || item.status === statusFilter;
+    }
+
+    // If feed statuses are available, check if any feed has the requested status
+    if (statusFilter === "all") {
+      return true; // Show all items when filter is "all"
+    }
+
+    // Check if any feed has the requested status
+    return item.feedStatuses.some((fs) => fs.status === statusFilter);
+  });
+
+  if (filteredItems.length === 0) {
+    return (
+      <div className="flex flex-col justify-center items-center p-8 space-y-2">
+        <p className="text-gray-500">No items found</p>
+        <p className="text-gray-400 text-sm">
+          comment with "!submit @{botId} #{feedId || "feedId"}" to start
+          curating
+        </p>
+      </div>
+    );
+  }
+
+  return (
+    <>
+      {filteredItems
+        .sort(
+          (a, b) =>
+            new Date(b.submittedAt!).getTime() -
+            new Date(a.submittedAt!).getTime(),
+        )
+        .map((item) => (
+          <FeedItem
+            key={item.tweetId}
+            submission={item}
+            statusFilter={statusFilter || "all"}
+          />
+        ))}
+    </>
+  );
+};
+
+export default SubmissionList;
diff --git a/frontend/src/index.css b/frontend/src/index.css
index 85d6b42a..8f6e4afa 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -40,7 +40,7 @@
 @layer components {
   /* Card styles */
   .card {
-    @apply bg-white p-6 border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-shadow duration-200 translate-x-0 translate-y-0 hover:-translate-x-0.5 hover:-translate-y-0.5;
+    @apply bg-white p-4 sm:p-6 border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-shadow duration-200 translate-x-0 translate-y-0 hover:-translate-x-0.5 hover:-translate-y-0.5 overflow-x-hidden max-w-full;
   }
 
   /* Button styles */
@@ -76,9 +76,14 @@
   }
 }
 
-/* Scrollbar Styles */
 /* Scrollbar Styles */
 @layer utilities {
+  .overflow-wrap-anywhere {
+    overflow-wrap: anywhere;
+    word-break: break-word;
+    hyphens: auto;
+  }
+  
   .custom-scrollbar {
     scrollbar-width: thin;
     scrollbar-color: black transparent;
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts
index 8722dd20..f84fa171 100644
--- a/frontend/src/lib/api.ts
+++ b/frontend/src/lib/api.ts
@@ -1,4 +1,4 @@
-import { useMutation, useQuery } from "@tanstack/react-query";
+import { useMutation, useQuery, useInfiniteQuery } from "@tanstack/react-query";
 import type { AppConfig, FeedConfig } from "../types/config";
 import type { TwitterSubmissionWithFeedData } from "../types/twitter";
 
@@ -106,3 +106,75 @@ export function useLeaderboard() {
     },
   });
 }
+
+export interface PaginationMetadata {
+  page: number;
+  limit: number;
+  totalCount: number;
+  totalPages: number;
+  hasNextPage: boolean;
+}
+
+export interface PaginatedResponse<T> {
+  items: T[];
+  pagination: PaginationMetadata;
+}
+
+// Define the return type of the transformed data
+export interface TransformedInfiniteData<T> {
+  pages: PaginatedResponse<T>[];
+  pageParams: number[];
+  items: T[];
+}
+
+export function useAllSubmissions(limit: number = 20, status?: string) {
+  // Use infinite query for direct pagination from the backend
+  return useInfiniteQuery<
+    PaginatedResponse<TwitterSubmissionWithFeedData>,
+    Error,
+    TransformedInfiniteData<TwitterSubmissionWithFeedData>,
+    [string, string | undefined],
+    number
+  >({
+    queryKey: ["all-submissions-paginated", status],
+    queryFn: async ({ pageParam = 0 }) => {
+      const statusParam = status ? `status=${status}` : "";
+      const pageParamStr = `page=${pageParam}`;
+      const limitParam = `limit=${limit}`;
+
+      // Build query string with available parameters
+      const queryParams = [statusParam, pageParamStr, limitParam]
+        .filter((param) => param !== "")
+        .join("&");
+
+      const url = `/api/submissions${queryParams ? `?${queryParams}` : ""}`;
+
+      const response = await fetch(url);
+      if (!response.ok) {
+        throw new Error("Failed to fetch submissions");
+      }
+
+      return response.json();
+    },
+    initialPageParam: 0,
+    getNextPageParam: (lastPage) => {
+      // Use the pagination metadata to determine if there's a next page
+      return lastPage.pagination.hasNextPage
+        ? lastPage.pagination.page + 1
+        : undefined;
+    },
+    // Transform the response to extract just the items for components that expect an array
+    select: (data) => ({
+      pages: data.pages,
+      pageParams: data.pageParams,
+      // Add a flattened items array for easier access
+      items: data.pages.flatMap((page) => page.items),
+    }),
+    // Poll every 10 seconds
+    refetchInterval: 10000,
+    // Refetch on window focus
+    refetchOnWindowFocus: true,
+    // Refetch when regaining network connection
+    refetchOnReconnect: true,
+  });
+}
diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts
index 29d83b19..a9a459db 100644
--- a/frontend/src/routeTree.gen.ts
+++ b/frontend/src/routeTree.gen.ts
@@ -14,7 +14,8 @@ import { Route as rootRoute } from "./routes/__root";
 import { Route as TestImport } from "./routes/test";
 import { Route as SettingsImport } from "./routes/settings";
 import { Route as IndexImport } from "./routes/index";
-import { Route as FeedFeedIdImport } from "./routes/feed.$feedId";
+import { Route as FeedIndexImport } from "./routes/feed/index";
+import { Route as FeedFeedIdImport } from "./routes/feed/$feedId";
 
 // Create/Update Routes
 
@@ -36,6 +37,12 @@ const IndexRoute = IndexImport.update({
   getParentRoute: () => rootRoute,
 } as any);
 
+const FeedIndexRoute = FeedIndexImport.update({
+  id: "/feed/",
+  path: "/feed/",
+  getParentRoute: () => rootRoute,
+} as any);
+
 const FeedFeedIdRoute = FeedFeedIdImport.update({
   id: "/feed/$feedId",
   path: "/feed/$feedId",
@@ -74,6 +81,13 @@ declare module "@tanstack/react-router" {
       preLoaderRoute: typeof FeedFeedIdImport;
       parentRoute: typeof rootRoute;
     };
+    "/feed/": {
+      id: "/feed/";
+      path: "/feed";
+      fullPath: "/feed";
+      preLoaderRoute: typeof FeedIndexImport;
+      parentRoute: typeof rootRoute;
+    };
   }
 }
 
@@ -84,6 +98,7 @@ export interface FileRoutesByFullPath {
   "/settings": typeof SettingsRoute;
   "/test": typeof TestRoute;
   "/feed/$feedId": typeof FeedFeedIdRoute;
+  "/feed": typeof FeedIndexRoute;
 }
 
 export interface FileRoutesByTo {
@@ -91,6 +106,7 @@ export interface FileRoutesByTo {
   "/settings": typeof SettingsRoute;
   "/test": typeof TestRoute;
   "/feed/$feedId": typeof FeedFeedIdRoute;
+  "/feed": typeof FeedIndexRoute;
 }
 
 export interface FileRoutesById {
@@ -99,14 +115,15 @@ export interface FileRoutesById {
   "/settings": typeof SettingsRoute;
   "/test": typeof TestRoute;
   "/feed/$feedId": typeof FeedFeedIdRoute;
+  "/feed/": typeof FeedIndexRoute;
 }
 
 export interface FileRouteTypes {
   fileRoutesByFullPath: FileRoutesByFullPath;
-  fullPaths: "/" | "/settings" | "/test" | "/feed/$feedId";
+  fullPaths: "/" | "/settings" | "/test" | "/feed/$feedId" | "/feed";
   fileRoutesByTo: FileRoutesByTo;
-  to: "/" | "/settings" | "/test" | "/feed/$feedId";
-  id: "__root__" | "/" | "/settings" | "/test" | "/feed/$feedId";
+  to: "/" | "/settings" | "/test" | "/feed/$feedId" | "/feed";
+  id: "__root__" | "/" | "/settings" | "/test" | "/feed/$feedId" | "/feed/";
   fileRoutesById: FileRoutesById;
 }
 
@@ -115,6 +132,7 @@ export interface RootRouteChildren {
   SettingsRoute: typeof SettingsRoute;
   TestRoute: typeof TestRoute;
   FeedFeedIdRoute: typeof FeedFeedIdRoute;
+  FeedIndexRoute: typeof FeedIndexRoute;
 }
 
 const rootRouteChildren: RootRouteChildren = {
@@ -122,6 +140,7 @@ const rootRouteChildren: RootRouteChildren = {
   SettingsRoute: SettingsRoute,
   TestRoute: TestRoute,
   FeedFeedIdRoute: FeedFeedIdRoute,
+  FeedIndexRoute: FeedIndexRoute,
 };
 
 export const routeTree = rootRoute
@@ -137,7 +156,8 @@ export const routeTree = rootRoute
         "/",
         "/settings",
         "/test",
-        "/feed/$feedId"
+        "/feed/$feedId",
+        "/feed/"
       ]
     },
     "/": {
@@ -150,7 +170,10 @@ export const routeTree = rootRoute
       "filePath": "test.tsx"
     },
     "/feed/$feedId": {
-      "filePath": "feed.$feedId.tsx"
+      "filePath": "feed/$feedId.tsx"
+    },
+    "/feed/": {
+      "filePath": "feed/index.tsx"
     }
   }
 }
diff --git a/frontend/src/routes/feed.$feedId.tsx b/frontend/src/routes/feed.$feedId.tsx
deleted file mode 100644
index 9a3fec9f..00000000
--- a/frontend/src/routes/feed.$feedId.tsx
+++ /dev/null
@@ -1,239 +0,0 @@
-import { createFileRoute } from "@tanstack/react-router";
-import FeedItem from "../components/FeedItem";
-import FeedList from "../components/FeedList";
-import Layout from "../components/Layout";
-import { Modal } from "../components/Modal";
-import { useFeedConfig, useFeedItems } from "../lib/api";
-import { useState } from "react";
-import { TwitterSubmission } from "../types/twitter";
-import { useBotId } from "../lib/config";
-import { getTwitterIntentUrl } from "../lib/twitter";
-
-export const Route = createFileRoute("/feed/$feedId")({
-  component: FeedPage,
-});
-
-function FeedPage() {
-  const { feedId } = Route.useParams();
-  const { data: feed } = useFeedConfig(feedId);
-  const { data: items = [] } = useFeedItems(feedId);
-  const botId = useBotId();
-  const [statusFilter, setStatusFilter] = useState<
-    "all" | TwitterSubmission["status"]
-  >("all");
-  const [isDownloadModalOpen, setIsDownloadModalOpen] = useState(false);
-
-  const handleDownload = (
-    selectedStatus: "all" | TwitterSubmission["status"],
-  ) => {
-    const itemsToDownload = items.filter(
-      (item) => selectedStatus === "all" || item.status === selectedStatus,
-    );
-    const jsonContent = JSON.stringify(itemsToDownload, null, 2);
-    const blob = new Blob([jsonContent], { type: "application/json" });
-    const url = URL.createObjectURL(blob);
-    const a = document.createElement("a");
-    a.href = url;
-    a.download = `${feed?.name || "feed"}_${selectedStatus}_submissions.json`;
-    document.body.appendChild(a);
-    a.click();
-    document.body.removeChild(a);
-    URL.revokeObjectURL(url);
-    setIsDownloadModalOpen(false);
-  };
-
-  const DownloadModal = () => (
-    <Modal
-      isOpen={isDownloadModalOpen}
-      onClose={() => setIsDownloadModalOpen(false)}
-    >
-      <h2 className="text-2xl font-bold mb-4">Download Submissions</h2>
-      <p className="mb-4">Select which submissions you want to download:</p>
-      <div className="space-y-2">
-        <button
-          onClick={() => handleDownload("all")}
-          className="w-full px-4 py-2 bg-gray-100 hover:bg-gray-200 text-black rounded-md border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-all duration-200 text-sm font-medium"
-        >
-          All Submissions
-        </button>
-        <button
-          onClick={() => handleDownload("approved")}
-          className="w-full px-4 py-2 bg-green-200 hover:bg-green-300 text-black rounded-md border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-all duration-200 text-sm font-medium"
-        >
-          Approved Submissions
-        </button>
-        <button
-          onClick={() => handleDownload("pending")}
-          className="w-full px-4 py-2 bg-yellow-200 hover:bg-yellow-300 text-black rounded-md border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-all duration-200 text-sm font-medium"
-        >
-          Pending Submissions
-        </button>
-        <button
-          onClick={() => handleDownload("rejected")}
-          className="w-full px-4 py-2 bg-red-200 hover:bg-red-300 text-black rounded-md border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-all duration-200 text-sm font-medium"
-        >
-          Rejected Submissions
-        </button>
-      </div>
-    </Modal>
-  );
-
-  const filteredItems = items.filter(
-    (item) => statusFilter === "all" || item.status === statusFilter,
-  );
-
-  const sidebarContent = (
-    <div className="p-2">
-      <FeedList />
-    </div>
-  );
-
-  const rightPanelContent = feed && (
-    <div className="space-y-8 max-w-full overflow-x-hidden">
-      {/* Moderation Box */}
-      <div className="p-1">
-        <h3 className="text-2xl mb-4">Moderation</h3>
-        <div className="space-y-4">
-          <div>
-            <div className="flex items-center justify-between mb-2">
-              <h4 className="font-medium">Approvers</h4>
-              <a
-                href={getTwitterIntentUrl({ action: "apply", botId, feedId })}
-                target="_blank"
-                rel="noopener noreferrer"
-                className="px-3 py-1.5 bg-gray-100 hover:bg-gray-200 text-black rounded-md border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-all duration-200 translate-x-0 translate-y-0 hover:-translate-x-0.5 hover:-translate-y-0.5 text-sm font-medium"
-              >
-                apply
-              </a>
-            </div>
-            <ul className="space-y-2">
-              {feed.moderation.approvers.twitter.map((handle) => (
-                <li key={handle}>
-                  <a
-                    href={`https://twitter.com/${handle}`}
-                    target="_blank"
-                    rel="noopener noreferrer"
-                    className="inline-flex items-center px-3 py-2 bg-white border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-shadow duration-200 translate-x-0 translate-y-0 hover:-translate-x-0.5 hover:-translate-y-0.5 text-sm font-mono w-full"
-                  >
-                    <span className="bg-blue-400 text-white text-xs px-1.5 py-0.5 rounded mr-2">
-                      Twitter
-                    </span>
-                    @{handle}
-                  </a>
-                </li>
-              ))}
-            </ul>
-          </div>
-        </div>
-      </div>
-
-      {/* Stream Box */}
-      <div className="p-1">
-        <h3 className="heading-3 mb-4">Stream</h3>
-        <div className="space-y-4">
-          <div className="p-4 bg-white border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-shadow duration-200 translate-x-0 translate-y-0 hover:-translate-x-0.5 hover:-translate-y-0.5">
-            <p className="text-center font-mono text-gray-500">
-              Coming soon...
-            </p>
-          </div>
-        </div>
-      </div>
-
-      {/* Recap Box */}
-      <div className="p-1">
-        <h3 className="heading-3 mb-4">Recap</h3>
-        <div className="space-y-4">
-          <div className="p-4 bg-white border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-shadow duration-200 translate-x-0 translate-y-0 hover:-translate-x-0.5 hover:-translate-y-0.5">
-            <p className="text-center font-mono text-gray-500">
-              Coming soon...
-            </p>
-          </div>
-        </div>
-      </div>
-    </div>
-  );
-
-  return (
-    <Layout sidebar={sidebarContent} rightPanel={rightPanelContent}>
-      <DownloadModal />
-      <div className="space-y-4">
-        <div className="grid grid-cols-1 xl:grid-cols-2 gap-4 mb-6">
-          <div>
-            <h2 className="text-2xl font-bold mb-2">
-              {feed?.name || "Loading..."}
-            </h2>
-            <p className="text-sm sm:text-base text-gray-600">
-              {feed?.description || "No description available"}
-            </p>
-          </div>
-          <div className="flex flex-wrap gap-2 items-start xl:items-center xl:justify-end">
-            <button
-              onClick={() => setStatusFilter("all")}
-              className={`px-3 py-1.5 rounded-md border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-all duration-200 translate-x-0 translate-y-0 hover:-translate-x-0.5 hover:-translate-y-0.5 text-sm font-medium ${
-                statusFilter === "all"
-                  ? "bg-black text-white"
-                  : "bg-gray-100 hover:bg-gray-200 text-black"
-              }`}
-            >
-              All
-            </button>
-            <button
-              onClick={() => setStatusFilter("pending")}
-              className={`px-3 py-1.5 rounded-md border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-all duration-200 translate-x-0 translate-y-0 hover:-translate-x-0.5 hover:-translate-y-0.5 text-sm font-medium ${
-                statusFilter === "pending"
-                  ? "bg-yellow-200 text-black"
-                  : "bg-gray-100 hover:bg-gray-200 text-black"
-              }`}
-            >
-              Pending
-            </button>
-            <button
-              onClick={() => setStatusFilter("approved")}
-              className={`px-3 py-1.5 rounded-md border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-all duration-200 translate-x-0 translate-y-0 hover:-translate-x-0.5 hover:-translate-y-0.5 text-sm font-medium ${
-                statusFilter === "approved"
-                  ? "bg-green-200 text-black"
-                  : "bg-gray-100 hover:bg-gray-200 text-black"
-              }`}
-            >
-              Approved
-            </button>
-            <button
-              onClick={() => setStatusFilter("rejected")}
-              className={`px-3 py-1.5 rounded-md border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-all duration-200 translate-x-0 translate-y-0 hover:-translate-x-0.5 hover:-translate-y-0.5 text-sm font-medium ${
-                statusFilter === "rejected"
-                  ? "bg-red-200 text-black"
-                  : "bg-gray-100 hover:bg-gray-200 text-black"
-              }`}
-            >
-              Rejected
-            </button>
-            <button
-              onClick={() => setIsDownloadModalOpen(true)}
-              className="px-3 py-1.5 bg-blue-500 hover:bg-blue-600 text-white rounded-md border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-all duration-200 translate-x-0 translate-y-0 hover:-translate-x-0.5 hover:-translate-y-0.5 text-sm font-medium"
-            >
-              Download
-            </button>
-          </div>
-        </div>
-        {filteredItems.length === 0 ? (
-          <div className="flex flex-col justify-center items-center p-8 space-y-2">
-            <p className="text-gray-500">No items found</p>
-            <p className="text-gray-400 text-sm">
-              comment with "!submit @{botId} #{feed?.id}" to start curating
-            </p>
-          </div>
-        ) : (
-          filteredItems
-            .sort(
-              (a, b) =>
-                new Date(b.submittedAt!).getTime() -
-                new Date(a.submittedAt!).getTime(),
-            )
-            .map((item) => <FeedItem key={item.tweetId} submission={item} />)
-        )}
-      </div>
-    </Layout>
-  );
-}
-
-export default FeedPage;
diff --git a/frontend/src/routes/feed/$feedId.tsx b/frontend/src/routes/feed/$feedId.tsx
new file mode 100644
index 00000000..f94fe1f0
--- /dev/null
+++ b/frontend/src/routes/feed/$feedId.tsx
@@ -0,0 +1,118 @@
+import { createFileRoute } from "@tanstack/react-router";
+import FeedList from "../../components/FeedList";
+import Layout from "../../components/Layout";
+import { useState } from "react";
+import { TwitterSubmission } from "../../types/twitter";
+import { useFeedConfig, useFeedItems } from "../../lib/api";
+import { useBotId } from "../../lib/config";
+import { getTwitterIntentUrl } from "../../lib/twitter";
+import FeedHeader from "../../components/FeedHeader";
+import SubmissionList from "../../components/SubmissionList";
+
+export const Route = createFileRoute("/feed/$feedId")({
+  component: FeedPage,
+});
+
+function FeedPage() {
+  const { feedId } = Route.useParams();
+  const { data: feed } = useFeedConfig(feedId);
+  const { data: items = [] } = useFeedItems(feedId);
+  const botId = useBotId();
+  const [statusFilter, setStatusFilter] = useState<
+    "all" | TwitterSubmission["status"]
+  >("all");
+
+  const sidebarContent = (
+    <div className="p-2">
+      <FeedList selectedFeedId={feedId} />
+    </div>
+  );
+
+  const rightPanelContent = feed && (
+    <div className="space-y-8 max-w-full overflow-x-hidden">
+      {/* Moderation Box */}
+      <div className="p-1">
+        <h3 className="text-2xl mb-4">Moderation</h3>
+        <div className="space-y-4">
+          <div>
+            <div className="flex items-center justify-between mb-2">
+              <h4 className="font-medium">Approvers</h4>
+              <a
+                href={getTwitterIntentUrl({ action: "apply", botId, feedId })}
+                target="_blank"
+                rel="noopener noreferrer"
+                className="px-3 py-1.5 bg-gray-100 hover:bg-gray-200 text-black rounded-md border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-all duration-200 translate-x-0 translate-y-0 hover:-translate-x-0.5 hover:-translate-y-0.5 text-sm font-medium"
+              >
+                apply
+              </a>
+            </div>
+            <ul className="space-y-2">
+              {feed.moderation.approvers.twitter.map((handle) => (
+                <li key={handle}>
+                  <a
+                    href={`https://twitter.com/${handle}`}
+                    target="_blank"
+                    rel="noopener noreferrer"
+                    className="inline-flex items-center px-3 py-2 bg-white border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-shadow duration-200 translate-x-0 translate-y-0 hover:-translate-x-0.5 hover:-translate-y-0.5 text-sm font-mono w-full"
+                  >
+                    <span className="bg-blue-400 text-white text-xs px-1.5 py-0.5 rounded mr-2">
+                      Twitter
+                    </span>
+                    @{handle}
+                  </a>
+                </li>
+              ))}
+            </ul>
+          </div>
+        </div>
+      </div>
+
+      {/* Stream Box */}
+      <div className="p-1">
+        <h3 className="heading-3 mb-4">Stream</h3>
+        <div className="space-y-4">
+          <div className="p-4 bg-white border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-shadow duration-200 translate-x-0 translate-y-0 hover:-translate-x-0.5 hover:-translate-y-0.5">
+            <p className="text-center font-mono text-gray-500">
+              Coming soon...
+            </p>
+          </div>
+        </div>
+      </div>
+
+      {/* Recap Box */}
+      <div className="p-1">
+        <h3 className="heading-3 mb-4">Recap</h3>
+        <div className="space-y-4">
+          <div className="p-4 bg-white border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-shadow duration-200 translate-x-0 translate-y-0 hover:-translate-x-0.5 hover:-translate-y-0.5">
+            <p className="text-center font-mono text-gray-500">
+              Coming soon...
+            </p>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+
+  return (
+    <Layout sidebar={sidebarContent} rightPanel={rightPanelContent}>
+      <div className="space-y-4">
+        <FeedHeader
+          title={feed?.name || "Loading..."}
+          description={feed?.description || "No description available"}
+          items={items}
+          statusFilter={statusFilter}
+          setStatusFilter={setStatusFilter}
+          feedName={feed?.name}
+        />
+        <SubmissionList
+          items={items}
+          statusFilter={statusFilter}
+          botId={botId}
+          feedId={feedId}
+        />
+      </div>
+    </Layout>
+  );
+}
+
+export default FeedPage;
diff --git a/frontend/src/routes/feed/index.tsx b/frontend/src/routes/feed/index.tsx
new file mode 100644
index 00000000..38a0e4ed
--- /dev/null
+++ b/frontend/src/routes/feed/index.tsx
@@ -0,0 +1,89 @@
+import { createFileRoute } from "@tanstack/react-router";
+import FeedList from "../../components/FeedList";
+import Layout from "../../components/Layout";
+import { useState } from "react";
+import { TwitterSubmission } from "../../types/twitter";
+import { useBotId } from "../../lib/config";
+import { useAllSubmissions } from "../../lib/api";
+import FeedHeader from "../../components/FeedHeader";
+import SubmissionList from "../../components/SubmissionList";
+import InfiniteFeed from "../../components/InfiniteFeed";
+
+export const Route = createFileRoute("/feed/")({
+  component: FeedIndexPage,
+});
+
+function FeedIndexPage() {
+  const botId = useBotId();
+  const [statusFilter, setStatusFilter] = useState<
+    "all" | TwitterSubmission["status"]
+  >("all");
+
+  // Fetch submissions with infinite scroll
+  const ITEMS_PER_PAGE = 20;
+  const { data, fetchNextPage, hasNextPage, isFetchingNextPage, status } =
+    useAllSubmissions(
+      ITEMS_PER_PAGE,
+      statusFilter === "all" ? undefined : statusFilter,
+    );
+
+  // Get the items from the transformed data
+  const items = data?.items || [];
+
+  const sidebarContent = (
+    <div className="p-2">
+      <FeedList />
+    </div>
+  );
+
+  // Simple right panel for the all feeds view
+  const rightPanelContent = (
+    <div className="space-y-8 max-w-full overflow-x-hidden">
+      <div className="p-1">
+        <h3 className="text-2xl mb-4">All Submissions</h3>
+        <div className="space-y-4">
+          <div className="p-4 bg-white border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-shadow duration-200 translate-x-0 translate-y-0 hover:-translate-x-0.5 hover:-translate-y-0.5">
+            <p className="text-gray-600">
+              This view shows all submissions across all feeds.
+            </p>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+
+  return (
+    <Layout sidebar={sidebarContent} rightPanel={rightPanelContent}>
+      <div className="space-y-4">
+        <FeedHeader
+          title="All Submissions"
+          description="View all submissions across all feeds"
+          items={items}
+          statusFilter={statusFilter}
+          setStatusFilter={setStatusFilter}
+          feedName="all"
+        />
+
+        <InfiniteFeed
+          items={items}
+          fetchNextPage={fetchNextPage}
+          hasNextPage={hasNextPage}
+          isFetchingNextPage={isFetchingNextPage}
+          status={status}
+          loadingMessage="Loading more submissions..."
+          noMoreItemsMessage="No more submissions to load"
+          initialLoadingMessage="Loading submissions..."
+          renderItems={(items) => (
+            <SubmissionList
+              items={items}
+              statusFilter={statusFilter}
+              botId={botId}
+            />
+          )}
+        />
+      </div>
+    </Layout>
+  );
+}
+
+export default FeedIndexPage;
diff --git a/frontend/src/routes/index.tsx b/frontend/src/routes/index.tsx
index 98d7a1c8..f02e5ef1 100644
--- a/frontend/src/routes/index.tsx
+++ b/frontend/src/routes/index.tsx
@@ -2,21 +2,10 @@ import { createFileRoute, redirect } from "@tanstack/react-router";
 
 export const Route = createFileRoute("/")({
   beforeLoad: async () => {
-    // Fetch feeds and redirect to the first one
-    const response = await fetch("/api/feeds");
-    if (!response.ok) {
-      throw new Error("Failed to fetch feeds");
-    }
-    const feeds = await response.json();
-
-    if (feeds.length > 0) {
-      throw redirect({
-        to: "/feed/$feedId",
-        params: { feedId: feeds[0].id },
-      });
-    }
-
-    return null;
+    // Redirect to the feed index page
+    throw redirect({
+      to: "/feed",
+    });
   },
   component: Index,
 });


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🧹 Nitpick comments (16)</summary><blockquote>

<details>
<summary>frontend/src/components/FeedHeader.tsx (1)</summary><blockquote>

`38-40`: **Consider adding aria-label for accessibility**

The download button container could benefit from additional accessibility attributes to improve screen reader support.

```diff
-<div className="w-full sm:w-auto mt-2 sm:mt-0">
+<div className="w-full sm:w-auto mt-2 sm:mt-0" aria-label="Download options">
   <DownloadButton items={items} feedName={feedName} />
 </div>
```

</blockquote></details>
<details>
<summary>frontend/src/components/StatusFilterButtons.tsx (2)</summary><blockquote>

`13-54`: **Consider refactoring repetitive button code**

The component contains four very similar button implementations that differ only in their status values and conditional styling. Consider extracting this to a reusable button component or using an array to map over status options.

```diff
-<div className="flex flex-wrap gap-2 items-start xl:items-center xl:justify-end">
-  <button
-    onClick={() => setStatusFilter("all")}
-    className={`px-3 py-1.5 rounded-md border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-all duration-200 translate-x-0 translate-y-0 hover:-translate-x-0.5 hover:-translate-y-0.5 text-sm font-medium ${
-      statusFilter === "all"
-        ? "bg-black text-white"
-        : "bg-gray-100 hover:bg-gray-200 text-black"
-    }`}
-  >
-    All
-  </button>
-  <button
-    onClick={() => setStatusFilter("pending")}
-    className={`px-3 py-1.5 rounded-md border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-all duration-200 translate-x-0 translate-y-0 hover:-translate-x-0.5 hover:-translate-y-0.5 text-sm font-medium ${
-      statusFilter === "pending"
-        ? "bg-yellow-200 text-black"
-        : "bg-gray-100 hover:bg-gray-200 text-black"
-    }`}
-  >
-    Pending
-  </button>
-  <button
-    onClick={() => setStatusFilter("approved")}
-    className={`px-3 py-1.5 rounded-md border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-all duration-200 translate-x-0 translate-y-0 hover:-translate-x-0.5 hover:-translate-y-0.5 text-sm font-medium ${
-      statusFilter === "approved"
-        ? "bg-green-200 text-black"
-        : "bg-gray-100 hover:bg-gray-200 text-black"
-    }`}
-  >
-    Approved
-  </button>
-  <button
-    onClick={() => setStatusFilter("rejected")}
-    className={`px-3 py-1.5 rounded-md border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-all duration-200 translate-x-0 translate-y-0 hover:-translate-x-0.5 hover:-translate-y-0.5 text-sm font-medium ${
-      statusFilter === "rejected"
-        ? "bg-red-200 text-black"
-        : "bg-gray-100 hover:bg-gray-200 text-black"
-    }`}
-  >
-    Rejected
-  </button>
+<div className="flex flex-wrap gap-2 items-start xl:items-center xl:justify-end">
+  {[
+    { value: "all", label: "All", activeClass: "bg-black text-white" },
+    { value: "pending", label: "Pending", activeClass: "bg-yellow-200 text-black" },
+    { value: "approved", label: "Approved", activeClass: "bg-green-200 text-black" },
+    { value: "rejected", label: "Rejected", activeClass: "bg-red-200 text-black" }
+  ].map(({ value, label, activeClass }) => (
+    <button
+      key={value}
+      onClick={() => setStatusFilter(value as any)}
+      className={`px-3 py-1.5 rounded-md border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-all duration-200 translate-x-0 translate-y-0 hover:-translate-x-0.5 hover:-translate-y-0.5 text-sm font-medium ${
+        statusFilter === value
+          ? activeClass
+          : "bg-gray-100 hover:bg-gray-200 text-black"
+      }`}
+    >
+      {label}
+    </button>
+  ))}
</div>
```

---

`14-23`: **Consider adding aria-pressed for accessibility**

Adding the `aria-pressed` attribute to the filter buttons would improve accessibility by indicating the current selected state to screen readers.

```diff
<button
  onClick={() => setStatusFilter("all")}
+ aria-pressed={statusFilter === "all"}
  className={`px-3 py-1.5 rounded-md border-2 border-black shadow-sharp hover:shadow-sharp-hover transition-all duration-200 translate-x-0 translate-y-0 hover:-translate-x-0.5 hover:-translate-y-0.5 text-sm font-medium ${
    statusFilter === "all"
      ? "bg-black text-white"
      : "bg-gray-100 hover:bg-gray-200 text-black"
  }`}
>
  All
</button>
```

</blockquote></details>
<details>
<summary>frontend/src/components/SubmissionList.tsx (1)</summary><blockquote>

`50-55`: **Handle potential null values in submittedAt.**

The sorting logic uses non-null assertion (`!`) on submittedAt which could lead to runtime errors if this property is ever null or undefined.


```diff
  .sort(
    (a, b) =>
-     new Date(b.submittedAt!).getTime() -
-     new Date(a.submittedAt!).getTime(),
+     new Date(b.submittedAt || new Date()).getTime() -
+     new Date(a.submittedAt || new Date()).getTime(),
  )
```

</blockquote></details>
<details>
<summary>frontend/src/components/DownloadButton.tsx (2)</summary><blockquote>

`16-33`: **Consider enhancing download filter to match SubmissionList behavior.**

The current implementation only filters based on the main status (`item.status`), unlike the SubmissionList component which also considers feed statuses. This might lead to inconsistency between what users see and what they download.


```diff
  const handleDownload = (
    selectedStatus: "all" | TwitterSubmission["status"],
  ) => {
    const itemsToDownload = items.filter(
-     (item) => selectedStatus === "all" || item.status === selectedStatus,
+     (item) => {
+       if (selectedStatus === "all") return true;
+       
+       // If no feed statuses, use the main status
+       if (!item.feedStatuses || item.feedStatuses.length === 0) {
+         return item.status === selectedStatus;
+       }
+       
+       // Check if any feed has the requested status
+       return item.feedStatuses.some((fs) => fs.status === selectedStatus);
+     },
    );
    const jsonContent = JSON.stringify(itemsToDownload, null, 2);
    const blob = new Blob([jsonContent], { type: "application/json" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = `${feedName}_${selectedStatus}_submissions.json`;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
    setIsDownloadModalOpen(false);
  };
```

---

`25-31`: **Add error handling for download operation.**

The current implementation doesn't handle potential errors during the DOM manipulation for download. Adding a try/catch block would make the code more robust.


```diff
    const blob = new Blob([jsonContent], { type: "application/json" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
+   try {
      a.href = url;
      a.download = `${feedName}_${selectedStatus}_submissions.json`;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
+   } catch (error) {
+     console.error("Download failed:", error);
+   } finally {
      URL.revokeObjectURL(url);
      setIsDownloadModalOpen(false);
+   }
```

</blockquote></details>
<details>
<summary>frontend/src/components/InfiniteFeed.tsx (1)</summary><blockquote>

`57-62`: **Remove or conditionally show debug logs.**

Production code should not contain console.log statements. Consider removing them or restricting them to development environments.


```diff
-  // Debug log to help diagnose the issue
-  console.log("InfiniteFeed debug:", {
-    itemsLength: items.length,
-    hasNextPage,
-    isFetchingNextPage,
-    status,
-  });
+  // Only log in development environment
+  if (process.env.NODE_ENV === 'development') {
+    console.log("InfiniteFeed debug:", {
+      itemsLength: items.length,
+      hasNextPage,
+      isFetchingNextPage,
+      status,
+    });
+  }
```

</blockquote></details>
<details>
<summary>backend/src/routes/api/submission.ts (1)</summary><blockquote>

`30-49`: **Consider database-level pagination for better performance**

The current implementation fetches all submissions into memory before applying sorting and pagination. This approach could become inefficient with large datasets.


Consider modifying the database service to handle sorting and pagination at the database level:

```diff
- // Get all submissions with the given status
- const allSubmissions = db.getAllSubmissions(status);
- 
- // Sort submissions by submittedAt date (newest first)
- allSubmissions.sort(
-   (a, b) =>
-     new Date(b.submittedAt!).getTime() - new Date(a.submittedAt!).getTime(),
- );
- 
- // Get total count for pagination metadata
- const totalCount = allSubmissions.length;
- 
- // Apply pagination
- const startIndex = page * limit;
- const endIndex = startIndex + limit;
- const paginatedSubmissions = allSubmissions.slice(startIndex, endIndex);

+ // Get total count for pagination metadata
+ const totalCount = db.getSubmissionsCount(status);
+ 
+ // Get paginated and sorted submissions directly from the database
+ const paginatedSubmissions = db.getPaginatedSubmissions({
+   status,
+   page,
+   limit,
+   sortBy: 'submittedAt',
+   sortOrder: 'desc'
+ });
```

This would require updating the database service to support these operations, but would significantly improve performance with large datasets.

</blockquote></details>
<details>
<summary>frontend/src/components/FeedItem.tsx (3)</summary><blockquote>

`66-77`: **Conditional link rendering is well-structured.**
When `feedId` is present, making the badge clickable by returning a `<Link>` is intuitive. Consider protective checks for empty or invalid `feedId`.

```diff
 if (feedId && feedId.trim() !== "") {
   return (
     <Link
       to="/feed/$feedId"
       ...
```

---

`82-210`: **Comprehensive status filtering and popover functionality.**
This new `FeedStatusBadges` component effectively filters feed statuses, handles responsive badge layouts, and manages a popover with `isExpanded`. Recommendations:
• Consider providing ARIA or keyboard navigation for the popover to enhance accessibility.  
• Using `"mousedown"` for closing may conflict with text selection outside the component; consider `"click"` if your UX allows.  
Overall, the component is well-structured and user-friendly.

---

`225-231`: **Potential clarity improvement in title fallback.**
Changing "Moderation Notes" to "Moderated" and defaulting to "Curated" can be confusing. A short in-line comment or a dedicated helper would make the conditional logic more transparent.

</blockquote></details>
<details>
<summary>backend/src/services/db/queries.ts (1)</summary><blockquote>

`499-499`: **Consider optional chaining for cleaner checks.**
Replacing multiple `&&` checks with `?.` can simplify the condition without changing functionality.

```diff
- if (result.sf && result.sf.feedId && result.f && result.f.id) {
+ if (result.sf?.feedId && result.f?.id) {
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 499-499: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

</blockquote></details>
<details>
<summary>frontend/src/routeTree.gen.ts (4)</summary><blockquote>

`40-44`: **Introduced FeedIndexRoute with trailing slash path.**
Using `"/feed/"` here might create inconsistencies. Consider using `"/feed"` to avoid potential collisions or confusion.

```diff
- path: "/feed/",
+ path: "/feed"
```

---

`109-109`: **Consistent route references.**
Pointing `"/feed"` in `fileRoutesByTo` is good for direct linking. Just ensure your path definitions remain uniform to prevent trailing slash issues.

---

`123-126`: **Mixed usage of '/feed' vs. '/feed/'.**
Including both variants in `fullPaths`, `to`, and `id` could be ambiguous for route resolution. Standardize on one form to keep it simple.

---

`159-160`: **Route manifest extensions.**
The manifest now includes `"/feed/$feedId"` and `"/feed/"`. Keep the slash usage consistent with your final approach to avoid routing confusion.



Also applies to: 173-175

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between c9bdcdf511c7804b3593bc26567de2f6fc388ccb and dca0a81c01ee567e3f7e37fdea9477ab252f999c.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `frontend/bun.lockb` is excluded by `!**/bun.lockb`

</details>

<details>
<summary>📒 Files selected for processing (20)</summary>

* `backend/src/routes/api/submission.ts` (2 hunks)
* `backend/src/services/db/index.ts` (2 hunks)
* `backend/src/services/db/queries.ts` (4 hunks)
* `backend/src/types/twitter.ts` (2 hunks)
* `frontend/package.json` (2 hunks)
* `frontend/src/components/DownloadButton.tsx` (1 hunks)
* `frontend/src/components/FeedHeader.tsx` (1 hunks)
* `frontend/src/components/FeedItem.tsx` (6 hunks)
* `frontend/src/components/FeedList.tsx` (2 hunks)
* `frontend/src/components/InfiniteFeed.tsx` (1 hunks)
* `frontend/src/components/Layout.tsx` (1 hunks)
* `frontend/src/components/StatusFilterButtons.tsx` (1 hunks)
* `frontend/src/components/SubmissionList.tsx` (1 hunks)
* `frontend/src/index.css` (2 hunks)
* `frontend/src/lib/api.ts` (2 hunks)
* `frontend/src/routeTree.gen.ts` (8 hunks)
* `frontend/src/routes/feed.$feedId.tsx` (0 hunks)
* `frontend/src/routes/feed/$feedId.tsx` (1 hunks)
* `frontend/src/routes/feed/index.tsx` (1 hunks)
* `frontend/src/routes/index.tsx` (1 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (1)</summary>

* frontend/src/routes/feed.$feedId.tsx

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>backend/src/services/db/queries.ts</summary>

[error] 499-499: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (43)</summary><blockquote>

<details>
<summary>frontend/src/components/FeedHeader.tsx (3)</summary>

`8-15`: **Good use of TypeScript interface for props**

The `FeedHeaderProps` interface is well-defined with clear types for each property. The use of union types for `statusFilter` helps ensure type safety when filtering submissions.

---

`26-42`: **Well-structured responsive layout**

The component uses a responsive grid layout that adapts from single column on smaller screens to two columns on extra-large screens. The use of flex layouts for the filter buttons and download section also ensures good mobile responsiveness.

---

`32-37`: **Proper component composition**

The `StatusFilterButtons` component is cleanly integrated with its required props passed down appropriately, facilitating the filter functionality mentioned in the PR objectives.

</details>
<details>
<summary>backend/src/types/twitter.ts (2)</summary>

`1-6`: **Well-designed FeedStatus interface**

The `FeedStatus` interface is well-structured with clear property names and types, supporting the PR objective of filtering submissions by status.

---

`26-26`: **Good data model enhancement**

Adding the optional `feedStatuses` array to `TwitterSubmissionWithFeedData` enables a more flexible data structure that can associate multiple feed statuses with a single Twitter submission. This supports the filtering capability mentioned in the PR objectives.

</details>
<details>
<summary>frontend/src/components/Layout.tsx (3)</summary>

`72-72`: **Enhanced footer with responsive styling**

The updated footer classes now include backdrop blur effect and responsive padding, which improves the visual appearance across different screen sizes.

---

`74-91`: **Improved footer layout with better mobile responsiveness**

The footer now uses a flex layout on mobile that places elements side by side, while maintaining centered text on larger screens. This enhances usability on smaller devices while addressing the PR objective of fixing the feedback link (issue #29).

---

`83-90`: **Fixed feedback link positioning**

The feedback link now has proper positioning with absolute placement on larger screens, which directly addresses issue #29 mentioned in the PR objectives.

</details>
<details>
<summary>frontend/src/components/StatusFilterButtons.tsx (1)</summary>

`3-6`: **Clear and concise interface definition**

The `StatusFilterButtonsProps` interface is well-defined with appropriate types, ensuring type safety for the status filtering functionality.

</details>
<details>
<summary>frontend/src/index.css (2)</summary>

`43-43`: **Good mobile-responsive card improvements!**

The updated `.card` class now has better responsive behavior with `p-4` on small screens and `sm:p-6` on larger screens. The addition of `overflow-x-hidden` and `max-w-full` also helps prevent content overflow issues, which is especially important for mobile layouts.

---

`81-85`: **Well-implemented text wrapping utility class**

The new `.overflow-wrap-anywhere` utility class is a great addition that will help handle long text content (like URLs, long words, or strings without spaces) more gracefully across the application. This supports the PR objective of improving UI, particularly for mobile responsive designs.

</details>
<details>
<summary>frontend/src/routes/index.tsx (1)</summary>

`5-8`: **Simplified routing logic is a good improvement**

The redirect to the "/feed" route has been simplified by removing unnecessary API calls and conditional logic. This change aligns well with the PR objective of implementing an "All" feed as the new default route, making the application's entry point more straightforward.

</details>
<details>
<summary>frontend/package.json (1)</summary>

`15-15`: <details>
<summary>✅ Verification successful</summary>

**Updated @tanstack/react-router dependencies**

The updates to the @tanstack router packages (from v1.97.0 to v1.114.15) are significant version jumps that likely support the new routing features implemented in this PR. These updates may have introduced important new features or bug fixes needed for the new "All" feed functionality.





Also applies to: 30-31

---

🌐 Web query:

```web
What changes were introduced between @tanstack/react-router v1.97.0 and v1.114.15?
```

💡 Result:

Between **@tanstack/react-router v1.97.0** and **v1.114.15**, several significant changes were introduced, focusing on structural refactoring, improved type safety, and expanded framework support. Here's a breakdown of key updates:

---

### 1. **Core Package Refactoring**
- **Agnostic Core Packages**: Middleware, isomorphic functions, and server/client logic were moved to framework-agnostic packages like `@tanstack/start-client-core` and `@tanstack/start-server-core` for better reusability across frameworks (Solid, React, etc.)[1][4].
- **Server Functions**: Refactored server-function handling to use `start-server-core`, simplifying integration with server-side rendering (SSR)[1][5].

---

### 2. **DevTools Enhancements**
- **Framework-Specific DevTools**: Added explicit dependencies for `solid-router-devtools` and `react-router-devtools`, ensuring smoother debugging experiences[1].
- **Structural Sharing**: Optimized route object reuse to reduce re-renders and improve performance[2][6].

---

### 3. **Type Safety Improvements**
- **Stricter Type Enforcement**: Enhanced type inference for routes, search parameters, and navigation, reducing runtime errors[6][10].
- **Route Validation**: Added runtime validation for path parameters (e.g., ensuring `userId` is required in `/user/:userId`)[6].

---

### 4. **Router Configuration**
- **Simplified API**: Streamlined route definitions using `createRouter` and `createRoute`, with nested routes declared via `addChildren`[5][6].
- **File-Based Routing**: Introduced optional file-based routing via Vite/CLI plugins, coexisting with code-based routing[10].

---

### 5. **Server-Side Rendering (SSR)**
- **Memory History Support**: Added `createMemoryHistory` for non-browser environments (e.g., SSR, testing)[8].
- **Streaming SSR**: Improved support for streaming SSR with React Suspense integration[4][7].

---

### 6. **Search Parameter Handling**
- **Stateful Search Params**: Treat search parameters as first-class state with type-safe parsing/validation (e.g., JSON serialization)[10].
- **Middleware for Search Params**: Added hooks for custom parsing and validation[4][10].

---

### 7. **Bug Fixes & Dependency Updates**
- **React 18 Compatibility**: Addressed issues with strict mode and concurrent rendering[1][9].
- **Dependency Conflicts**: Resolved TypeScript errors during upgrades (e.g., `@types/react` version mismatches)[12].

---

### Comparison Table: Key Features Added/Improved
| Feature                     | v1.97.0 | v1.114.15 | Source |
|-----------------------------|---------|-----------|--------|
| Agnostic Core Packages      | ❌      | ✅        | [1][5] |
| Type-Safe Search Params     | Basic   | Advanced  | [2][10]|
| Framework-Specific DevTools | ❌      | ✅        | [1]    |
| SSR Streaming               | ❌      | ✅        | [4][7] |
| Runtime Param Validation    | ❌      | ✅        | [6]    |

---

For a full migration guide, refer to the [official documentation](https://tanstack.com/router/latest/docs/framework/react/migrate-from-react-router)[9].

Citations:

- 1: https://github.com/TanStack/router/releases
- 2: https://www.dhiwise.com/post/react-router-vstanstack-router-a-detailed-comparison
- 3: https://bundlephobia.com/package/@tanstack/react-router
- 4: https://tanstack.com/router/v1/docs/framework/react/comparison
- 5: https://tanstack.com/router/v1/docs/framework/react/quick-start
- 6: https://dev.to/rigalpatel001/tanstack-router-the-future-of-react-routing-in-2025-421p
- 7: https://www.youtube.com/watch?v=KkrS_wfFq2I
- 8: https://tanstack.com/router/v1/docs/framework/react/guide/history-types
- 9: https://tanstack.com/router/latest/docs/framework/react/migrate-from-react-router
- 10: https://tanstack.com/router/v1/docs/framework/react/overview
- 11: https://tanstack.com/router
- 12: https://github.com/TanStack/router/issues/2276

---



</details>

**Verified Dependency Update: @tanstack/react-router v1.114.15**

The upgrade from v1.97.0 to v1.114.15 has been confirmed to introduce several key improvements—such as core package refactoring, enhanced type safety, improved SSR support, and various bug fixes (including React 18 compatibility and dependency conflict resolutions). These changes are in line with the new routing features required for the "All" feed functionality.

- **File Affected:** `frontend/package.json` (lines 15 and similarly at 30-31)
- **Key Improvements:** 
  - Structural refactoring for better framework-agnostic support
  - Enhanced type safety and runtime validation for routes and parameters
  - Improved SSR support and streaming capabilities
  - Bug fixes that resolve issues with React 18 strict mode

These updates are appropriate and beneficial to the project's requirements.

</details>
<details>
<summary>frontend/src/components/FeedList.tsx (2)</summary>

`2-2`: **Improved component design with props-based approach**

Switching from URL parameters to a props-based approach for the `selectedFeedId` makes the `FeedList` component more reusable and maintainable. This change supports the new "All" feed implementation by decoupling the component from URL-specific context.



Also applies to: 6-6

---

`41-41`: **Consistent implementation of selectedFeedId prop**

The condition correctly uses the new `selectedFeedId` prop to determine the active feed, maintaining the same visual feedback as before but with the improved props-based architecture.

</details>
<details>
<summary>frontend/src/components/SubmissionList.tsx (2)</summary>

`21-34`: **The filtering logic looks well-structured and robust.**

The approach of checking for both item's main status and feed statuses is thorough. It correctly handles different scenarios: when feed statuses are missing, when "all" filter is selected, and when filtering by a specific status.

---

`36-46`: **Good user experience for empty state.**

The empty state is well-handled with informative messages and clear instructions for users. This provides good guidance on how to interact with the system when no items match the filter criteria.

</details>
<details>
<summary>frontend/src/components/InfiniteFeed.tsx (2)</summary>

`27-54`: **Well-implemented Intersection Observer for infinite scrolling.**

The observer setup is clean and efficient, with good threshold and rootMargin values. The cleanup is properly handled in the useEffect return function, which is important to prevent memory leaks.

---

`69-79`: **Good UX with distinct loading states and messages.**

The component handles different loading states well with customizable messages for initial loading, fetching more items, and when there are no more items to load.

</details>
<details>
<summary>backend/src/services/db/index.ts (2)</summary>

`16-16`: **LGTM: Added new type import for enhanced return data.**

Adding `TwitterSubmissionWithFeedData` type is appropriate for the new return value of `getAllSubmissions`, which now includes feed status information.

---

`83-85`: <details>
<summary>❓ Verification inconclusive</summary>

**Verify pagination impact from signature change.**

The `getAllSubmissions` method has been updated to accept only an optional status parameter, removing the previous pagination parameters. This might affect how pagination is handled elsewhere in the application.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check for all usages of getAllSubmissions to ensure pagination is handled correctly

echo "Searching for getAllSubmissions usages in the codebase:"
rg -B3 -A3 "getAllSubmissions\(" --type ts
```

Length of output: 1581

---



</details>

**Ensure Consistent Pagination Handling After Method Signature Update**

The updated `getAllSubmissions` method in `backend/src/services/db/index.ts` now accepts only an optional `status` parameter, which removes the previous pagination parameters. However, note that in `backend/src/routes/api/submission.ts` the query still extracts `page` and `limit` (along with `status`), but only `status` is passed to `getAllSubmissions`. Please verify whether:

- **Pagination is now intended to be handled elsewhere:** If pagination is still desired, confirm that the API endpoint implements manual pagination on the sorted array.
- **No unintended impact exists:** If the removal of pagination support in the database query was intentional, update the API to remove the unused `page` and `limit` parameters.

</details>
<details>
<summary>frontend/src/routes/feed/index.tsx (2)</summary>

`1-90`: **Well-structured implementation of the "All" feed route**

This new file correctly implements the "All" feed functionality with status filtering and infinite scrolling. The component structure follows the application's design patterns and properly handles different states (loading, error, empty).

---

`67-83`: **Good implementation of infinite scrolling**

The `InfiniteFeed` component is well integrated with proper props for handling pagination, loading states, and rendering items. This will provide a smooth user experience when browsing through large sets of submissions.

</details>
<details>
<summary>frontend/src/lib/api.ts (2)</summary>

`110-128`: **Well-designed types for pagination**

These interface definitions provide a clear structure for pagination data and will help ensure type safety throughout the application.

---

`130-180`: <details>
<summary>✅ Verification successful</summary>

**Fix API endpoint URL construction**

There appears to be a mismatch between the API endpoint in this frontend code and the backend route.


```diff
-      const url = `/api/submissions${queryParams ? `?${queryParams}` : ""}`;
+      const url = `/api/submission${queryParams ? `?${queryParams}` : ""}`;
```

This change ensures the API calls will reach the correct backend endpoint.

---

🏁 Script executed:

```shell
#!/bin/bash
# Verify the correct API endpoint path in the backend

# Check if the submission endpoint is registered as /api/submission or /api/submissions
echo "Checking endpoint registration in backend files..."
rg -p "router\.(get|post)\(\s*['\"]/" --glob="backend/src/routes/api/*.ts" | grep -i submiss
```

Length of output: 298

---



</details>

**Action: Update API endpoint URL in `useAllSubmissions` hook**

Based on the verification output, the backend registers the submission endpoint as `/api/submission` (singular) in `backend/src/routes/api/submission.ts`. Please update the frontend code accordingly.

- **Change Required:** Modify the URL construction in `frontend/src/lib/api.ts`:
  ```diff
  -      const url = `/api/submissions${queryParams ? `?${queryParams}` : ""}`;
  +      const url = `/api/submission${queryParams ? `?${queryParams}` : ""}`;
  ```

</details>
<details>
<summary>frontend/src/routes/feed/$feedId.tsx (1)</summary>

`1-119`: **The 'FeedPage' functionality has been replaced with the new 'All' feed implementation**

According to the PR objectives, this file is being replaced by the new `/feed/index.tsx` implementation. The changes align with the PR's goal of implementing an "All" feed and improving the UI.

</details>
<details>
<summary>backend/src/routes/api/submission.ts (2)</summary>

`13-62`: **Well-structured API endpoint with pagination and filtering**

The implementation of the GET endpoint for submissions with optional status filtering and pagination is well-designed. The use of zod for request validation is a good practice.

---

`81-98`: **Good implementation of feed-specific submissions with optional status filtering**

The endpoint for retrieving submissions by feed is properly implemented with status filtering capability, which aligns with the PR objectives.

</details>
<details>
<summary>frontend/src/components/FeedItem.tsx (7)</summary>

`1-6`: **Great job adding necessary icons and feed types.**
These imports provide the icons and type definitions relevant to the new feed functionality, which looks correct and well-organized.

---

`9-10`: **Helpful hooks for UI state and routing.**
Introducing React hooks and `Link` from `@tanstack/react-router` is appropriate for managing local state and navigation in the new feed UI.

---

`49-56`: **Optional feed references augment the StatusBadge's reusability.**
Allowing `feedId` and `feedName` to be optional ensures that the badge can act as a clickable link when these props are provided. This design pattern increases flexibility.

---

`315-321`: **Appropriate fallback logic.**
Displaying `FeedStatusBadges` only when feed statuses are present ensures a neat UI. The fallback to the single main status is consistent and clear.

---

`336-340`: **Potential XSS concern when rendering user-generated content.**
If `submission.content` originates from untrusted input, consider sanitizing it to avoid cross-site scripting. Where possible, verify that it only contains safe text.

Would you like a script to trace how `submission.content` is constructed across the codebase to confirm safety?

---

`348-358`: **Solid integration of moderation details.**
Displaying the most recent moderation note is a valuable addition, helping everyone see the final decision context.

---

`365-369`: **Clear curator notes presentation.**
Including the curator’s notes provides transparency regarding submission context. Straightforward and user-friendly approach.

</details>
<details>
<summary>backend/src/services/db/queries.ts (5)</summary>

`9-9`: **New import for FeedStatus.**
This expands your data models for feed-specific status tracking, aligning well with the new functionality.

---

`355-355`: **Optional 'status' parameter introduction.**
Including an optional status filter in `getAllSubmissions` enhances its versatility. Ensure calling code handles cases where status is undefined.

---

`357-451`: **Conditional query building for status filtering.**
Filtering by `submissionFeeds.status` when a status is provided is a clear approach. Verify that submissions without feed associations don’t get excluded unintentionally if they’re relevant.

Would you like a script to detect any submissions lacking feed associations to see if they’re filtered out?

---

`455-515`: **Efficient grouping logic with Maps.**
Bundling data into `submissionMap` and `feedStatusMap` before final compilation is performant and avoids repeated loops.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 499-499: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

---

`517-566`: **Priority-based status assignment logic.**
Giving “pending” the highest precedence, followed by “rejected,” and finally “approved” is straightforward and clearly implemented. Double-check this hierarchy aligns with business requirements.

</details>
<details>
<summary>frontend/src/routeTree.gen.ts (3)</summary>

`17-18`: **New feed route imports.**
Importing separate routes for the feed index (`index.tsx`) and feed ID (`$feedId.tsx`) keeps the routing structure modular and organized.

---

`101-101`: **Alias route for feed index.**
Declaring `"/feed"` tied to `FeedIndexRoute` is helpful, but confirm it matches the actual route definition to avoid mismatched paths.

---

`135-143`: **New feed index route entry.**
This addition properly incorporates `FeedIndexRoute` in the root route’s children. Implementation is straightforward.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-13T05:47:54Z

Review comments:
diff_hunk: @@ -99,14 +115,15 @@ export interface FileRoutesById {
   "/settings": typeof SettingsRoute;
   "/test": typeof TestRoute;
   "/feed/$feedId": typeof FeedFeedIdRoute;
+  "/feed/": typeof FeedIndexRoute;
path: frontend/src/routeTree.gen.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Potential duplication with "/feed/".**
Having both `"/feed"` and `"/feed/"` in the IDs may lead to accidental duplication. Consolidate them for clarity.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-13T05:47:53Z
author_association: NONE
diff_hunk: @@ -74,6 +81,13 @@ declare module "@tanstack/react-router" {
       preLoaderRoute: typeof FeedFeedIdImport;
       parentRoute: typeof rootRoute;
     };
+    "/feed/": {
+      id: "/feed/";
+      path: "/feed";
+      fullPath: "/feed";
+      preLoaderRoute: typeof FeedIndexImport;
+      parentRoute: typeof rootRoute;
+    };
path: frontend/src/routeTree.gen.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Duplicated route path or mismatch.**
The mismatch between `id: "/feed/"` and `path: "/feed"` can cause confusion. Unify them to prevent unexpected routing collisions.

```diff
  "/feed/": {
    id: "/feed",
    path: "/feed",
    ...
  },
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    "/feed/": {
      id: "/feed";
      path: "/feed";
      fullPath: "/feed";
      preLoaderRoute: typeof FeedIndexImport;
      parentRoute: typeof rootRoute;
    };
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-13T05:47:53Z
author_association: NONE

PR comments:
user: login: vercel[bot]
created_at: 2025-03-13T05:42:56Z
body: [vc]: #q0UJWvUCOoCT9xEsTlIOYtBuKNrWtQRY5Lqr91o7OcY=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJjdXJhdGVkb3RmdW4tYXBwIiwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3BvdGxvY2svY3VyYXRlZG90ZnVuLWFwcC9DNTRCcDFXOXM0RWk1V3BpaVJ2TXh3ajdteVhyIiwicHJldmlld1VybCI6ImN1cmF0ZWRvdGZ1bi1hcHAtZ2l0LWZlYXQtYWxsLWZlZWQtcG90bG9jay52ZXJjZWwuYXBwIiwibmV4dENvbW1pdFN0YXR1cyI6IkRFUExPWUVEIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6ImN1cmF0ZWRvdGZ1bi1hcHAtZ2l0LWZlYXQtYWxsLWZlZWQtcG90bG9jay52ZXJjZWwuYXBwIn0sInJvb3REaXJlY3RvcnkiOiJmcm9udGVuZCJ9XX0=
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **curatedotfun-app** | ✅ Ready ([Inspect](https://vercel.com/potlock/curatedotfun-app/C54Bp1W9s4Ei5WpiiRvMxwj7myXr)) | [Visit Preview](https://curatedotfun-app-git-feat-all-feed-potlock.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/curatedotfun-app-git-feat-all-feed-potlock.vercel.app?via=pr-comment-feedback-link) | Mar 13, 2025 6:04am |


author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-03-13T05:43:01Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: failure by coderabbit.ai -->

> [!CAUTION]
> ## Review failed
> 
> The pull request is closed.

<!-- end of auto-generated comment: failure by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
This update enhances both backend and frontend functionality for handling Twitter submissions. On the backend, submission retrieval now supports optional status filtering and a simplified pagination model, and data models have been enriched with additional feed information. On the frontend, several new components are introduced—including those for infinite scrolling, download functionality, and status filtering—and route definitions have been updated to support a smooth feed page interface with dynamic feed IDs.

## Changes

| File(s) | Change Summary |
| --- | --- |
| `backend/src/routes/api/submission.ts`, `backend/src/services/db/index.ts`, `backend/src/services/db/queries.ts`, `backend/src/types/twitter.ts` | Enhanced submission retrieval: added optional status filtering with Zod validation, replaced offset pagination with page/limit handling, updated query logic to include feed details, and introduced the new `FeedStatus` interface and enriched `TwitterSubmissionWithFeedData` type. |
| `frontend/package.json`, `frontend/src/index.css` | Updated dependencies (upgraded `@tanstack/react-router` and replaced removed devtools plugin) and improved styling (refined card padding and added an `overflow-wrap-anywhere` utility). |
| `frontend/src/components/DownloadButton.tsx`, `frontend/src/components/FeedHeader.tsx`, `frontend/src/components/FeedItem.tsx`, `frontend/src/components/FeedList.tsx`, `frontend/src/components/InfiniteFeed.tsx`, `frontend/src/components/StatusFilterButtons.tsx`, `frontend/src/components/SubmissionList.tsx` | Introduced and updated UI components that provide download functionality, feed header display, interactive status badges, feed list selection via props, infinite scrolling, and filtering controls. |
| `frontend/src/lib/api.ts`, `frontend/src/routeTree.gen.ts`, `frontend/src/routes/feed/$feedId.tsx`, `frontend/src/routes/feed/index.tsx`, `frontend/src/routes/index.tsx`, *(removed: `frontend/src/routes/feed.$feedId.tsx`)* | Revised routing and API integration: added new dynamic feed routes and a dedicated feed index route, simplified redirection logic to `/feed`, and introduced the `useAllSubmissions` hook to support infinite querying of submissions. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
  participant U as User
  participant IF as InfiniteFeed Component
  participant IO as IntersectionObserver
  participant API as useAllSubmissions Hook
  
  U->>IF: Scrolls to bottom of page
  IF->>IO: Observe target element
  IO-->>IF: Target in view
  IF->>API: Trigger fetchNextPage (if hasNextPage)
  API-->>IF: Return next page of submissions
  IF-->>U: Render additional items
```

```mermaid
sequenceDiagram
  participant U as User
  participant DB as DownloadButton Component
  participant DM as DownloadModal
  
  U->>DB: Clicks "Download" button
  DB->>DM: Opens download modal
  U->>DM: Selects a status filter (e.g., "Approved")
  DM->>DB: Triggers handleDownload with selected status
  DB->>DB: Filters submissions and creates JSON blob
  DB-->>U: Initiates file download
```

## Possibly related PRs
- **PotLock/curatedotfun#64**: Updates the `getAllSubmissions` method to accept a status parameter and return enriched data, which directly connects to the changes in submission retrieval and filtering in this PR.
- **PotLock/curatedotfun#63**: Involves modifications to the `getAllSubmissions` method in the `DatabaseService` class, specifically updating its parameters and return type, linking it to the changes in this PR.

## Poem
> I'm a little rabbit with coding delight,  
> Hopping through submissions by day and by night.  
> With new status filters and pages set anew,  
> My data flows smooth like a fresh morning dew.  
> Infinite scroll and downloads—oh what a sight!  
> In every line of code, my heart feels light.  
> 🐇 Happy hops in our updated digital flight!

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- announcements_start -->

> [!TIP]
> <details>
> <summary>⚡🧪 Multi-step agentic review comment chat (experimental)</summary>
> 
> - We're introducing multi-step agentic chat in review comments. This experimental feature enhances review discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments.
> 						- To enable this feature, set early_access to true under in the settings.
> 
> </details>

<!-- announcements_end -->

---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between dca0a81c01ee567e3f7e37fdea9477ab252f999c and 055d8d34d1412b50cfa66390d8306525fdf5fecb.

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `backend/src/services/db/queries.ts` (4 hunks)
* `frontend/src/components/DownloadButton.tsx` (1 hunks)
* `frontend/src/components/FeedItem.tsx` (6 hunks)
* `frontend/src/components/InfiniteFeed.tsx` (1 hunks)
* `frontend/src/components/StatusFilterButtons.tsx` (1 hunks)

</details>

</details>
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=PotLock/curatedotfun&utm_content=88):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE