From 6f600c9a5cfc3f6bf579107a65cb3b5ace00cf12 Mon Sep 17 00:00:00 2001 From: Mattias Tall Date: Tue, 26 May 2026 16:41:22 +0200 Subject: [PATCH] UX: list view everywhere, mobile polish, affinity dismissal fix - Default list view across all pages (Home, Following, History, Queue, ContinueWatching, Liked, Discovery, SearchResults, Channel) - Watch.jsx mobile: smaller chips/title/avatar/meta, hide tags + keyboard hint on mobile, tighter gaps, compact description padding - Fix mobile bottom nav showing focus outline on tap - Fix _update_affinity to write negative entries (not just positive) so dislikes/dismissals on unseen content actually register - Dismissing a discovery video now fires -3.0 affinity against its tags, matching the dislike weight Co-Authored-By: Claude Sonnet 4.6 --- backend/routers/discovery.py | 4 ++++ backend/routers/videos.py | 3 +-- frontend/src/components/Layout.jsx | 2 +- frontend/src/pages/Channel.jsx | 4 ++-- frontend/src/pages/ContinueWatching.jsx | 8 +++----- frontend/src/pages/Discovery.jsx | 3 ++- frontend/src/pages/Following.jsx | 4 ++-- frontend/src/pages/History.jsx | 4 ++-- frontend/src/pages/Home.jsx | 2 +- frontend/src/pages/Liked.jsx | 4 ++-- frontend/src/pages/Queue.jsx | 3 ++- frontend/src/pages/SearchResults.jsx | 4 ++-- frontend/src/pages/Watch.jsx | 22 +++++++++++----------- 13 files changed, 35 insertions(+), 32 deletions(-) diff --git a/backend/routers/discovery.py b/backend/routers/discovery.py index d36cfcb..655b20a 100644 --- a/backend/routers/discovery.py +++ b/backend/routers/discovery.py @@ -207,6 +207,10 @@ def dismiss_discovery_video( dq = db.query(DiscoveryQueue).filter_by(user_id=current_user.id, channel_id=channel_id).first() if dq: dq.seen = True + + from ..routers.videos import _update_affinity + _update_affinity(db, current_user.id, video, -3.0) + db.commit() diff --git a/backend/routers/videos.py b/backend/routers/videos.py index ee86c91..076dcdf 100644 --- a/backend/routers/videos.py +++ b/backend/routers/videos.py @@ -37,8 +37,7 @@ def _update_affinity(db: Session, user_id: int, video: Video, delta: float): existing.score = max(existing.score + delta, -20.0) existing.updated_at = datetime.utcnow() else: - if delta > 0: - db.add(UserTagAffinity(user_id=user_id, tag=tag, score=delta)) + db.add(UserTagAffinity(user_id=user_id, tag=tag, score=delta)) diff --git a/frontend/src/components/Layout.jsx b/frontend/src/components/Layout.jsx index 00fbbcb..cea54cf 100644 --- a/frontend/src/components/Layout.jsx +++ b/frontend/src/components/Layout.jsx @@ -40,7 +40,7 @@ function BottomNav({ newCount }) { to={tab.to} end={tab.end} className={({ isActive }) => - `relative flex-1 flex flex-col items-center justify-center gap-0.5 transition-colors ${ + `relative flex-1 flex flex-col items-center justify-center gap-0.5 transition-colors outline-none ${ isActive ? "text-accent" : "text-zinc-500" }` } diff --git a/frontend/src/pages/Channel.jsx b/frontend/src/pages/Channel.jsx index d9f09a8..7659bbf 100644 --- a/frontend/src/pages/Channel.jsx +++ b/frontend/src/pages/Channel.jsx @@ -164,9 +164,9 @@ export default function ChannelPage() {
-
+
{sortVideos(videos, videoSort).map((v) => ( - + ))}
diff --git a/frontend/src/pages/ContinueWatching.jsx b/frontend/src/pages/ContinueWatching.jsx index 3caf653..123b5fe 100644 --- a/frontend/src/pages/ContinueWatching.jsx +++ b/frontend/src/pages/ContinueWatching.jsx @@ -34,14 +34,12 @@ export default function ContinueWatchingPage() { ) : ( <>

{videos.length} video{videos.length !== 1 ? "s" : ""} in progress

-
+
{videos.map((v) => ( ))}
diff --git a/frontend/src/pages/Discovery.jsx b/frontend/src/pages/Discovery.jsx index b4434f7..b25c6ac 100644 --- a/frontend/src/pages/Discovery.jsx +++ b/frontend/src/pages/Discovery.jsx @@ -324,11 +324,12 @@ export default function DiscoveryPage() { ) : ( <> -
+
{visibleVideos.map((v) => ( ))} diff --git a/frontend/src/pages/Following.jsx b/frontend/src/pages/Following.jsx index 3ef680a..de99733 100644 --- a/frontend/src/pages/Following.jsx +++ b/frontend/src/pages/Following.jsx @@ -1006,8 +1006,8 @@ export default function Following() {

No videos indexed yet — hit Sync all to pull the latest from YouTube.

) : ( <> -
- {sortedFeed.map((v) => )} +
+ {sortedFeed.map((v) => )}
{hasMoreFeed && (
diff --git a/frontend/src/pages/History.jsx b/frontend/src/pages/History.jsx index fb7b7f3..30f3325 100644 --- a/frontend/src/pages/History.jsx +++ b/frontend/src/pages/History.jsx @@ -34,9 +34,9 @@ export default function History() {
) : ( <> -
+
{videos.map((v) => ( - + ))}
diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 7a2eeac..38cce0b 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -21,7 +21,7 @@ export default function Home() { const [dismissed, setDismissed] = useState(new Set()); const [shuffleKey, setShuffleKey] = useState(0); const [duration, setDuration] = useState(""); - const [viewMode, setViewMode] = useState(() => localStorage.getItem("home-view-mode") ?? "grid"); + const [viewMode, setViewMode] = useState(() => localStorage.getItem("home-view-mode") ?? "list"); const toggleViewMode = () => { const next = viewMode === "grid" ? "list" : "grid"; diff --git a/frontend/src/pages/Liked.jsx b/frontend/src/pages/Liked.jsx index cdb687f..8a01251 100644 --- a/frontend/src/pages/Liked.jsx +++ b/frontend/src/pages/Liked.jsx @@ -88,9 +88,9 @@ export default function LikedPage() {

) : ( -
+
{sortLiked(videos, sort).map((v) => ( - + ))}
)} diff --git a/frontend/src/pages/Queue.jsx b/frontend/src/pages/Queue.jsx index 77684b0..c16b4f2 100644 --- a/frontend/src/pages/Queue.jsx +++ b/frontend/src/pages/Queue.jsx @@ -36,11 +36,12 @@ export default function QueuePage() { ) : ( <>

{videos.length} video{videos.length !== 1 ? "s" : ""} saved

-
+
{videos.map((v) => ( { toggleQueue(v.id).then(() => qc.invalidateQueries({ queryKey: ["queue"] })); }} diff --git a/frontend/src/pages/SearchResults.jsx b/frontend/src/pages/SearchResults.jsx index d1351fe..1edee8c 100644 --- a/frontend/src/pages/SearchResults.jsx +++ b/frontend/src/pages/SearchResults.jsx @@ -108,9 +108,9 @@ export default function SearchResults() { {hasMore ? `${visibleCount} of ${videos.length}` : videos.length} -
+
{visibleVideos.map((v) => ( - + ))}
{hasMore && ( diff --git a/frontend/src/pages/Watch.jsx b/frontend/src/pages/Watch.jsx index 05e958e..efdb36c 100644 --- a/frontend/src/pages/Watch.jsx +++ b/frontend/src/pages/Watch.jsx @@ -71,10 +71,10 @@ function DescriptionBox({ text }) { return (
hasMore && setExpanded(v => !v)} > -

+

{linkify(displayed)}

{hasMore && ( @@ -94,7 +94,7 @@ function Chip({ onClick, active, disabled, children }) { onClick={onClick} disabled={disabled} className={[ - "flex items-center gap-1.5 px-4 py-2 rounded-full text-sm font-medium transition-colors", + "flex items-center gap-1 px-3 py-1.5 rounded-full text-xs font-medium transition-colors", active ? "bg-zinc-100 text-zinc-900 hover:bg-white" : "bg-zinc-800 text-zinc-300 hover:bg-zinc-700", disabled && "opacity-40 cursor-not-allowed", ].filter(Boolean).join(" ")} @@ -844,7 +844,7 @@ export default function Watch() {
{/* ── Left: video + info ───────────────────────────────────────────── */} -
+
{/* Player */}
@@ -887,11 +887,11 @@ export default function Watch() { )} {/* Title */} -

{title}

+

{title}

{/* Meta + actions row */}
-
+
{date && {date}} {video?.view_count > 0 && <>·{formatViews(video.view_count)}} {video?.like_count > 0 && <>·{formatViews(video.like_count).replace(" views", "")} likes} @@ -914,7 +914,7 @@ export default function Watch() { {/* Actions */} -
+
{!dlComplete && !isDownloading && !downloadMut.isPending && (