From aaac910e0c840fba348cf52dcd762e56867c7c98 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 7 Sep 2025 11:55:58 +0000 Subject: [PATCH 1/3] Initial plan From 54f0d2bd191cbfad3d0b28a5659d7ca2d72c6067 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 7 Sep 2025 12:04:13 +0000 Subject: [PATCH 2/3] Implement pin functionality for news stories - Add isPinned and pinnedOrder fields to CompletedStory interface - Add pin/unpin/remove actions to simpleLiveFeedStore - Update StoryCard to show pin/close buttons on hover - Add visual indicators for pinned stories - Sort stories with pinned ones first in LiveFeed - Add accessibility labels and proper interaction patterns Co-authored-by: acdc-digital <127530566+acdc-digital@users.noreply.github.com> --- smnb/components/livefeed/StoryCard.tsx | 55 +++++++++++- smnb/components/livefeed/liveFeed.tsx | 50 ++++++++++- .../stores/livefeed/simpleLiveFeedStore.ts | 86 +++++++++++++++++++ smnb/lib/utils/storyUtils.ts | 21 ++++- 4 files changed, 204 insertions(+), 8 deletions(-) diff --git a/smnb/components/livefeed/StoryCard.tsx b/smnb/components/livefeed/StoryCard.tsx index 6feabf0..340f413 100644 --- a/smnb/components/livefeed/StoryCard.tsx +++ b/smnb/components/livefeed/StoryCard.tsx @@ -13,14 +13,14 @@ interface StoryCardProps { className?: string; theme?: keyof typeof StoryThemes; showActions?: boolean; - onAction?: (action: 'read' | 'share' | 'bookmark', story: CompletedStory) => void; + onAction?: (action: 'read' | 'share' | 'bookmark' | 'pin' | 'unpin' | 'remove', story: CompletedStory) => void; } export default function StoryCard({ story, isFirst = false, className, - theme = isFirst ? 'highlighted' : 'default', + theme = story.isPinned ? 'pinned' : (isFirst ? 'highlighted' : 'default'), showActions = false, onAction }: StoryCardProps) { @@ -72,6 +72,13 @@ export default function StoryCard({ {story.tone} + {/* Pinned indicator */} + {story.isPinned && ( + + PINNED #{story.pinnedOrder} + + )} + {/* Thread indicators */} {story.isThreadUpdate && ( @@ -79,10 +86,52 @@ export default function StoryCard({ )} + {/* Action buttons - Pin and Remove (on hover) */} +
+ {onAction && ( + <> + {/* Pin/Unpin Button */} + + + {/* Remove Button */} + + + )} +
+ {/* Source attribution */} {story.originalItem?.subreddit && ( setIsBookmarked(!isBookmarked)} diff --git a/smnb/components/livefeed/liveFeed.tsx b/smnb/components/livefeed/liveFeed.tsx index 5c97375..1212bbd 100644 --- a/smnb/components/livefeed/liveFeed.tsx +++ b/smnb/components/livefeed/liveFeed.tsx @@ -5,7 +5,7 @@ import { useEffect, useState } from 'react'; import React from 'react'; -import { useSimpleLiveFeedStore } from '@/lib/stores/livefeed/simpleLiveFeedStore'; +import { useSimpleLiveFeedStore, CompletedStory } from '@/lib/stores/livefeed/simpleLiveFeedStore'; import { Trash2 } from 'lucide-react'; import StoryCard from './StoryCard'; @@ -20,8 +20,49 @@ export default function LiveFeed({ className }: LiveFeedProps) { storyHistory, clearStoryHistory, loadStoriesFromConvex, + pinStory, + unpinStory, + removeStory, } = useSimpleLiveFeedStore(); + // Sort stories with pinned ones first + const sortedStories = React.useMemo(() => { + return [...storyHistory].sort((a, b) => { + // Pinned stories first + if (a.isPinned && !b.isPinned) return -1; + if (!a.isPinned && b.isPinned) return 1; + + // If both pinned, sort by pinnedOrder + if (a.isPinned && b.isPinned) { + return (a.pinnedOrder || 0) - (b.pinnedOrder || 0); + } + + // For unpinned stories, maintain original order (newest first) + return b.timestamp.getTime() - a.timestamp.getTime(); + }); + }, [storyHistory]); + + // Handle story actions + const handleStoryAction = (action: 'read' | 'share' | 'bookmark' | 'pin' | 'unpin' | 'remove', story: CompletedStory) => { + switch (action) { + case 'pin': + pinStory(story.id); + break; + case 'unpin': + unpinStory(story.id); + break; + case 'remove': + removeStory(story.id); + break; + case 'read': + case 'share': + case 'bookmark': + // These actions can be handled later if needed + console.log(`${action} action for story:`, story.id); + break; + } + }; + // Check for reduced motion preference useEffect(() => { const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)'); @@ -44,7 +85,7 @@ export default function LiveFeed({ className }: LiveFeedProps) { {/* Fixed Header */}
- Live Stories {storyHistory.length > 0 ? `(${storyHistory.length})` : ''} + Live Stories {sortedStories.length > 0 ? `(${sortedStories.length})` : ''}