fix(frontend): scroll only on local actions

Amolith created

Previously, the selected item would scroll into view on every render,
including when receiving updates from other clients. Now scrolling only
occurs when the user performs a local action (voting, deleting, or
navigating with keyboard).

Assisted-by: Claude Sonnet 4.5 via Crush

Change summary

static/app.js | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)

Detailed changes

static/app.js 🔗

@@ -14,6 +14,7 @@ let selectedItemId = null; // For keyboard navigation
 let selectedPosition = null; // Position to restore after voting
 let lastSyncTime = null; // For connection status
 let isReady = false; // Track if initial state received
+let shouldScrollSelectedIntoView = false; // Only scroll on local actions
 
 const CHECK_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check"><polyline points="20 6 9 17 4 12"></polyline></svg>`;
 
@@ -322,6 +323,7 @@ function vote(itemId, voteType) {
   // Remember current position to select item at same position after re-sort
   const sorted = getSortedItems();
   selectedPosition = sorted.findIndex(i => i.id === itemId);
+  shouldScrollSelectedIntoView = true;
   
   // Keep the button visually pressed briefly to avoid flicker
   const btn = document.querySelector(`[data-item-id="${itemId}"] [data-action="vote"][data-vote-type="${voteType}"]`);
@@ -341,6 +343,7 @@ function vote(itemId, voteType) {
 
 function deleteItem(itemId) {
   if (confirm('Delete this item?')) {
+    shouldScrollSelectedIntoView = true;
     sendMessage({ type: 'delete_item', itemId });
   }
 }
@@ -457,12 +460,14 @@ document.addEventListener('keydown', (e) => {
     const currentIdx = sorted.findIndex(i => i.id === selectedItemId);
     const nextIdx = Math.min(currentIdx + 1, sorted.length - 1);
     selectedItemId = sorted[nextIdx].id;
+    shouldScrollSelectedIntoView = true;
     render();
   } else if (e.key === 'k' || e.key === 'ArrowUp') {
     e.preventDefault();
     const currentIdx = sorted.findIndex(i => i.id === selectedItemId);
     const prevIdx = Math.max(currentIdx - 1, 0);
     selectedItemId = sorted[prevIdx].id;
+    shouldScrollSelectedIntoView = true;
     render();
   }
   
@@ -674,12 +679,13 @@ function render() {
     }
   });
   
-  // Scroll selected item into view
-  if (selectedItemId) {
+  // Scroll selected item into view on local actions
+  if (shouldScrollSelectedIntoView && selectedItemId) {
     const selectedEl = listContainer.querySelector(`[data-item-id="${selectedItemId}"]`);
     if (selectedEl) {
       selectedEl.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
     }
+    shouldScrollSelectedIntoView = false;
   }
 }