From 1bed4bbd9a0606f9d3edfbf0ccfd1499793f8712 Mon Sep 17 00:00:00 2001
From: Adam Mathes <adam@adammathes.com>
Date: Mon, 16 Feb 2026 08:59:56 -0800
Subject: Fix v3 theme contrast and sync with v2 colors, add v3 logo, and fix
 v2 test stability

- Sync v3 dark/light theme colors with v2 defaults
- Fix v3 settings input/select contrast in dark mode
- Add logo emoji to v3 sidebar
- Fix duplicate key warnings and side-effect issues in FeedItems.tsx (v2)
- Rebuild production assets
---
 frontend/src/components/FeedItems.tsx | 54 +++++++++++++++++------------------
 1 file changed, 26 insertions(+), 28 deletions(-)

(limited to 'frontend/src/components/FeedItems.tsx')

diff --git a/frontend/src/components/FeedItems.tsx b/frontend/src/components/FeedItems.tsx
index dc94cfd..2c3253b 100644
--- a/frontend/src/components/FeedItems.tsx
+++ b/frontend/src/components/FeedItems.tsx
@@ -89,9 +89,13 @@ export default function FeedItems() {
         }
         return res.json();
       })
-      .then((data) => {
+      .then((data: Item[]) => {
         if (maxId) {
-          setItems((prev) => [...prev, ...data]);
+          setItems((prev) => {
+            const existingIds = new Set(prev.map(i => i._id));
+            const newItems = data.filter(i => !existingIds.has(i._id));
+            return [...prev, ...newItems];
+          });
         } else {
           setItems(data);
         }
@@ -148,49 +152,43 @@ export default function FeedItems() {
 
   useEffect(() => {
     const handleKeyDown = (e: KeyboardEvent) => {
-      // Use ref to get latest items without effect re-running
       const currentItems = itemsRef.current;
       if (currentItems.length === 0) return;
 
       if (e.key === 'j') {
-        setSelectedIndex((prev) => {
-          const nextIndex = Math.min(prev + 1, currentItems.length - 1);
-          if (nextIndex !== prev) {
-            const item = currentItems[nextIndex];
-            if (!item.read) {
-              markAsRead(item);
-            }
-            scrollToItem(nextIndex);
+        const nextIndex = Math.min(selectedIndex + 1, currentItems.length - 1);
+        if (nextIndex !== selectedIndex) {
+          setSelectedIndex(nextIndex);
+          const item = currentItems[nextIndex];
+          if (!item.read) {
+            markAsRead(item);
           }
+          scrollToItem(nextIndex);
 
           // Trigger load more if needed
           if (nextIndex === currentItems.length - 1 && hasMoreRef.current && !loadingMoreRef.current) {
             fetchItems(String(currentItems[currentItems.length - 1]._id));
           }
-
-          return nextIndex;
-        });
+        } else if (hasMoreRef.current && !loadingMoreRef.current) {
+          // Already at last item, but more can be loaded
+          fetchItems(String(currentItems[currentItems.length - 1]._id));
+        }
       } else if (e.key === 'k') {
-        setSelectedIndex((prev) => {
-          const nextIndex = Math.max(prev - 1, 0);
-          if (nextIndex !== prev) {
-            scrollToItem(nextIndex);
-          }
-          return nextIndex;
-        });
+        const nextIndex = Math.max(selectedIndex - 1, 0);
+        if (nextIndex !== selectedIndex) {
+          setSelectedIndex(nextIndex);
+          scrollToItem(nextIndex);
+        }
       } else if (e.key === 's') {
-        setSelectedIndex((currentIndex) => {
-          if (currentIndex >= 0 && currentIndex < currentItems.length) {
-            toggleStar(currentItems[currentIndex]);
-          }
-          return currentIndex;
-        });
+        if (selectedIndex >= 0 && selectedIndex < currentItems.length) {
+          toggleStar(currentItems[selectedIndex]);
+        }
       }
     };
 
     window.addEventListener('keydown', handleKeyDown);
     return () => window.removeEventListener('keydown', handleKeyDown);
-  }, [markAsRead, scrollToItem, toggleStar, fetchItems]);
+  }, [markAsRead, scrollToItem, toggleStar, fetchItems, selectedIndex]);
 
 
   // Stable Observer
-- 
cgit v1.2.3

